freebsd-update.sh revision 167189
1161748Scperciva#!/bin/sh
2161748Scperciva
3161748Scperciva#-
4161748Scperciva# Copyright 2004-2006 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 167189 2007-03-04 00:29:42Z 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
47161748Scperciva  -s server    -- Server from which to fetch updates
48161748Scperciva                  (default: update.FreeBSD.org)
49161748Scperciva  -t address   -- Mail output of cron command, if any, to address
50161748Scperciva                  (default: root)
51161748ScpercivaCommands:
52161748Scperciva  fetch        -- Fetch updates from server
53161748Scperciva  cron         -- Sleep rand(3600) seconds, fetch updates, and send an
54161748Scperciva                  email if updates were found
55161748Scperciva  install      -- Install downloaded updates
56161748Scperciva  rollback     -- Uninstall most recently installed updates
57161748ScpercivaEOF
58161748Scperciva	exit 0
59161748Scperciva}
60161748Scperciva
61161748Scperciva#### Configuration processing functions
62161748Scperciva
63161748Scperciva#-
64161748Scperciva# Configuration options are set in the following order of priority:
65161748Scperciva# 1. Command line options
66161748Scperciva# 2. Configuration file options
67161748Scperciva# 3. Default options
68161748Scperciva# In addition, certain options (e.g., IgnorePaths) can be specified multiple
69161748Scperciva# times and (as long as these are all in the same place, e.g., inside the
70161748Scperciva# configuration file) they will accumulate.  Finally, because the path to the
71161748Scperciva# configuration file can be specified at the command line, the entire command
72161748Scperciva# line must be processed before we start reading the configuration file.
73161748Scperciva#
74161748Scperciva# Sound like a mess?  It is.  Here's how we handle this:
75161748Scperciva# 1. Initialize CONFFILE and all the options to "".
76161748Scperciva# 2. Process the command line.  Throw an error if a non-accumulating option
77161748Scperciva#    is specified twice.
78161748Scperciva# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
79161748Scperciva# 4. For all the configuration options X, set X_saved to X.
80161748Scperciva# 5. Initialize all the options to "".
81161748Scperciva# 6. Read CONFFILE line by line, parsing options.
82161748Scperciva# 7. For each configuration option X, set X to X_saved iff X_saved is not "".
83161748Scperciva# 8. Repeat steps 4-7, except setting options to their default values at (6).
84161748Scperciva
85161748ScpercivaCONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
86161748Scperciva    KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
87161748Scperciva    BASEDIR VERBOSELEVEL"
88161748Scperciva
89161748Scperciva# Set all the configuration options to "".
90161748Scpercivanullconfig () {
91161748Scperciva	for X in ${CONFIGOPTIONS}; do
92161748Scperciva		eval ${X}=""
93161748Scperciva	done
94161748Scperciva}
95161748Scperciva
96161748Scperciva# For each configuration option X, set X_saved to X.
97161748Scpercivasaveconfig () {
98161748Scperciva	for X in ${CONFIGOPTIONS}; do
99161748Scperciva		eval ${X}_saved=\$${X}
100161748Scperciva	done
101161748Scperciva}
102161748Scperciva
103161748Scperciva# For each configuration option X, set X to X_saved if X_saved is not "".
104161748Scpercivamergeconfig () {
105161748Scperciva	for X in ${CONFIGOPTIONS}; do
106161748Scperciva		eval _=\$${X}_saved
107161748Scperciva		if ! [ -z "${_}" ]; then
108161748Scperciva			eval ${X}=\$${X}_saved
109161748Scperciva		fi
110161748Scperciva	done
111161748Scperciva}
112161748Scperciva
113161748Scperciva# Set the trusted keyprint.
114161748Scpercivaconfig_KeyPrint () {
115161748Scperciva	if [ -z ${KEYPRINT} ]; then
116161748Scperciva		KEYPRINT=$1
117161748Scperciva	else
118161748Scperciva		return 1
119161748Scperciva	fi
120161748Scperciva}
121161748Scperciva
122161748Scperciva# Set the working directory.
123161748Scpercivaconfig_WorkDir () {
124161748Scperciva	if [ -z ${WORKDIR} ]; then
125161748Scperciva		WORKDIR=$1
126161748Scperciva	else
127161748Scperciva		return 1
128161748Scperciva	fi
129161748Scperciva}
130161748Scperciva
131161748Scperciva# Set the name of the server (pool) from which to fetch updates
132161748Scpercivaconfig_ServerName () {
133161748Scperciva	if [ -z ${SERVERNAME} ]; then
134161748Scperciva		SERVERNAME=$1
135161748Scperciva	else
136161748Scperciva		return 1
137161748Scperciva	fi
138161748Scperciva}
139161748Scperciva
140161748Scperciva# Set the address to which 'cron' output will be mailed.
141161748Scpercivaconfig_MailTo () {
142161748Scperciva	if [ -z ${MAILTO} ]; then
143161748Scperciva		MAILTO=$1
144161748Scperciva	else
145161748Scperciva		return 1
146161748Scperciva	fi
147161748Scperciva}
148161748Scperciva
149161748Scperciva# Set whether FreeBSD Update is allowed to add files (or directories, or
150161748Scperciva# symlinks) which did not previously exist.
151161748Scpercivaconfig_AllowAdd () {
152161748Scperciva	if [ -z ${ALLOWADD} ]; then
153161748Scperciva		case $1 in
154161748Scperciva		[Yy][Ee][Ss])
155161748Scperciva			ALLOWADD=yes
156161748Scperciva			;;
157161748Scperciva		[Nn][Oo])
158161748Scperciva			ALLOWADD=no
159161748Scperciva			;;
160161748Scperciva		*)
161161748Scperciva			return 1
162161748Scperciva			;;
163161748Scperciva		esac
164161748Scperciva	else
165161748Scperciva		return 1
166161748Scperciva	fi
167161748Scperciva}
168161748Scperciva
169161748Scperciva# Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
170161748Scpercivaconfig_AllowDelete () {
171161748Scperciva	if [ -z ${ALLOWDELETE} ]; then
172161748Scperciva		case $1 in
173161748Scperciva		[Yy][Ee][Ss])
174161748Scperciva			ALLOWDELETE=yes
175161748Scperciva			;;
176161748Scperciva		[Nn][Oo])
177161748Scperciva			ALLOWDELETE=no
178161748Scperciva			;;
179161748Scperciva		*)
180161748Scperciva			return 1
181161748Scperciva			;;
182161748Scperciva		esac
183161748Scperciva	else
184161748Scperciva		return 1
185161748Scperciva	fi
186161748Scperciva}
187161748Scperciva
188161748Scperciva# Set whether FreeBSD Update should keep existing inode ownership,
189161748Scperciva# permissions, and flags, in the event that they have been modified locally
190161748Scperciva# after the release.
191161748Scpercivaconfig_KeepModifiedMetadata () {
192161748Scperciva	if [ -z ${KEEPMODIFIEDMETADATA} ]; then
193161748Scperciva		case $1 in
194161748Scperciva		[Yy][Ee][Ss])
195161748Scperciva			KEEPMODIFIEDMETADATA=yes
196161748Scperciva			;;
197161748Scperciva		[Nn][Oo])
198161748Scperciva			KEEPMODIFIEDMETADATA=no
199161748Scperciva			;;
200161748Scperciva		*)
201161748Scperciva			return 1
202161748Scperciva			;;
203161748Scperciva		esac
204161748Scperciva	else
205161748Scperciva		return 1
206161748Scperciva	fi
207161748Scperciva}
208161748Scperciva
209161748Scperciva# Add to the list of components which should be kept updated.
210161748Scpercivaconfig_Components () {
211161748Scperciva	for C in $@; do
212161748Scperciva		COMPONENTS="${COMPONENTS} ${C}"
213161748Scperciva	done
214161748Scperciva}
215161748Scperciva
216161748Scperciva# Add to the list of paths under which updates will be ignored.
217161748Scpercivaconfig_IgnorePaths () {
218161748Scperciva	for C in $@; do
219161748Scperciva		IGNOREPATHS="${IGNOREPATHS} ${C}"
220161748Scperciva	done
221161748Scperciva}
222161748Scperciva
223161748Scperciva# Add to the list of paths within which updates will be performed only if the
224161748Scperciva# file on disk has not been modified locally.
225161748Scpercivaconfig_UpdateIfUnmodified () {
226161748Scperciva	for C in $@; do
227161748Scperciva		UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
228161748Scperciva	done
229161748Scperciva}
230161748Scperciva
231161748Scperciva# Work on a FreeBSD installation mounted under $1
232161748Scpercivaconfig_BaseDir () {
233161748Scperciva	if [ -z ${BASEDIR} ]; then
234161748Scperciva		BASEDIR=$1
235161748Scperciva	else
236161748Scperciva		return 1
237161748Scperciva	fi
238161748Scperciva}
239161748Scperciva
240161748Scperciva# Define what happens to output of utilities
241161748Scpercivaconfig_VerboseLevel () {
242161748Scperciva	if [ -z ${VERBOSELEVEL} ]; then
243161748Scperciva		case $1 in
244161748Scperciva		[Dd][Ee][Bb][Uu][Gg])
245161748Scperciva			VERBOSELEVEL=debug
246161748Scperciva			;;
247161748Scperciva		[Nn][Oo][Ss][Tt][Aa][Tt][Ss])
248161748Scperciva			VERBOSELEVEL=nostats
249161748Scperciva			;;
250161748Scperciva		[Ss][Tt][Aa][Tt][Ss])
251161748Scperciva			VERBOSELEVEL=stats
252161748Scperciva			;;
253161748Scperciva		*)
254161748Scperciva			return 1
255161748Scperciva			;;
256161748Scperciva		esac
257161748Scperciva	else
258161748Scperciva		return 1
259161748Scperciva	fi
260161748Scperciva}
261161748Scperciva
262161748Scperciva# Handle one line of configuration
263161748Scpercivaconfigline () {
264161748Scperciva	if [ $# -eq 0 ]; then
265161748Scperciva		return
266161748Scperciva	fi
267161748Scperciva
268161748Scperciva	OPT=$1
269161748Scperciva	shift
270161748Scperciva	config_${OPT} $@
271161748Scperciva}
272161748Scperciva
273161748Scperciva#### Parameter handling functions.
274161748Scperciva
275161748Scperciva# Initialize parameters to null, just in case they're
276161748Scperciva# set in the environment.
277161748Scpercivainit_params () {
278161748Scperciva	# Configration settings
279161748Scperciva	nullconfig
280161748Scperciva
281161748Scperciva	# No configuration file set yet
282161748Scperciva	CONFFILE=""
283161748Scperciva
284161748Scperciva	# No commands specified yet
285161748Scperciva	COMMANDS=""
286161748Scperciva}
287161748Scperciva
288161748Scperciva# Parse the command line
289161748Scpercivaparse_cmdline () {
290161748Scperciva	while [ $# -gt 0 ]; do
291161748Scperciva		case "$1" in
292161748Scperciva		# Location of configuration file
293161748Scperciva		-f)
294161748Scperciva			if [ $# -eq 1 ]; then usage; fi
295161748Scperciva			if [ ! -z "${CONFFILE}" ]; then usage; fi
296161748Scperciva			shift; CONFFILE="$1"
297161748Scperciva			;;
298161748Scperciva
299161748Scperciva		# Configuration file equivalents
300161748Scperciva		-b)
301161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
302161748Scperciva			config_BaseDir $1 || usage
303161748Scperciva			;;
304161748Scperciva		-d)
305161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
306161748Scperciva			config_WorkDir $1 || usage
307161748Scperciva			;;
308161748Scperciva		-k)
309161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
310161748Scperciva			config_KeyPrint $1 || usage
311161748Scperciva			;;
312161748Scperciva		-s)
313161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
314161748Scperciva			config_ServerName $1 || usage
315161748Scperciva			;;
316161748Scperciva		-t)
317161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
318161748Scperciva			config_MailTo $1 || usage
319161748Scperciva			;;
320161748Scperciva		-v)
321161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
322161748Scperciva			config_VerboseLevel $1 || usage
323161748Scperciva			;;
324161748Scperciva
325161748Scperciva		# Aliases for "-v debug" and "-v nostats"
326161748Scperciva		--debug)
327161748Scperciva			config_VerboseLevel debug || usage
328161748Scperciva			;;
329161748Scperciva		--no-stats)
330161748Scperciva			config_VerboseLevel nostats || usage
331161748Scperciva			;;
332161748Scperciva
333161748Scperciva		# Commands
334161748Scperciva		cron | fetch | install | rollback)
335161748Scperciva			COMMANDS="${COMMANDS} $1"
336161748Scperciva			;;
337161748Scperciva
338161748Scperciva		# Anything else is an error
339161748Scperciva		*)
340161748Scperciva			usage
341161748Scperciva			;;
342161748Scperciva		esac
343161748Scperciva		shift
344161748Scperciva	done
345161748Scperciva
346161748Scperciva	# Make sure we have at least one command
347161748Scperciva	if [ -z "${COMMANDS}" ]; then
348161748Scperciva		usage
349161748Scperciva	fi
350161748Scperciva}
351161748Scperciva
352161748Scperciva# Parse the configuration file
353161748Scpercivaparse_conffile () {
354161748Scperciva	# If a configuration file was specified on the command line, check
355161748Scperciva	# that it exists and is readable.
356161748Scperciva	if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
357161748Scperciva		echo -n "File does not exist "
358161748Scperciva		echo -n "or is not readable: "
359161748Scperciva		echo ${CONFFILE}
360161748Scperciva		exit 1
361161748Scperciva	fi
362161748Scperciva
363161748Scperciva	# If a configuration file was not specified on the command line,
364161748Scperciva	# use the default configuration file path.  If that default does
365161748Scperciva	# not exist, give up looking for any configuration.
366161748Scperciva	if [ -z "${CONFFILE}" ]; then
367161748Scperciva		CONFFILE="/etc/freebsd-update.conf"
368161748Scperciva		if [ ! -r "${CONFFILE}" ]; then
369161748Scperciva			return
370161748Scperciva		fi
371161748Scperciva	fi
372161748Scperciva
373161748Scperciva	# Save the configuration options specified on the command line, and
374161748Scperciva	# clear all the options in preparation for reading the config file.
375161748Scperciva	saveconfig
376161748Scperciva	nullconfig
377161748Scperciva
378161748Scperciva	# Read the configuration file.  Anything after the first '#' is
379161748Scperciva	# ignored, and any blank lines are ignored.
380161748Scperciva	L=0
381161748Scperciva	while read LINE; do
382161748Scperciva		L=$(($L + 1))
383161748Scperciva		LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
384161748Scperciva		if ! configline ${LINEX}; then
385161748Scperciva			echo "Error processing configuration file, line $L:"
386161748Scperciva			echo "==> ${LINE}"
387161748Scperciva			exit 1
388161748Scperciva		fi
389161748Scperciva	done < ${CONFFILE}
390161748Scperciva
391161748Scperciva	# Merge the settings read from the configuration file with those
392161748Scperciva	# provided at the command line.
393161748Scperciva	mergeconfig
394161748Scperciva}
395161748Scperciva
396161748Scperciva# Provide some default parameters
397161748Scpercivadefault_params () {
398161748Scperciva	# Save any parameters already configured, and clear the slate
399161748Scperciva	saveconfig
400161748Scperciva	nullconfig
401161748Scperciva
402161748Scperciva	# Default configurations
403161748Scperciva	config_WorkDir /var/db/freebsd-update
404161748Scperciva	config_MailTo root
405161748Scperciva	config_AllowAdd yes
406161748Scperciva	config_AllowDelete yes
407161748Scperciva	config_KeepModifiedMetadata yes
408161748Scperciva	config_BaseDir /
409161748Scperciva	config_VerboseLevel stats
410161748Scperciva
411161748Scperciva	# Merge these defaults into the earlier-configured settings
412161748Scperciva	mergeconfig
413161748Scperciva}
414161748Scperciva
415161748Scperciva# Set utility output filtering options, based on ${VERBOSELEVEL}
416161748Scpercivafetch_setup_verboselevel () {
417161748Scperciva	case ${VERBOSELEVEL} in
418161748Scperciva	debug)
419161748Scperciva		QUIETREDIR="/dev/stderr"
420161748Scperciva		QUIETFLAG=" "
421161748Scperciva		STATSREDIR="/dev/stderr"
422161748Scperciva		DDSTATS=".."
423161748Scperciva		XARGST="-t"
424161748Scperciva		NDEBUG=" "
425161748Scperciva		;;
426161748Scperciva	nostats)
427161748Scperciva		QUIETREDIR=""
428161748Scperciva		QUIETFLAG=""
429161748Scperciva		STATSREDIR="/dev/null"
430161748Scperciva		DDSTATS=".."
431161748Scperciva		XARGST=""
432161748Scperciva		NDEBUG=""
433161748Scperciva		;;
434161748Scperciva	stats)
435161748Scperciva		QUIETREDIR="/dev/null"
436161748Scperciva		QUIETFLAG="-q"
437161748Scperciva		STATSREDIR="/dev/stdout"
438161748Scperciva		DDSTATS=""
439161748Scperciva		XARGST=""
440161748Scperciva		NDEBUG="-n"
441161748Scperciva		;;
442161748Scperciva	esac
443161748Scperciva}
444161748Scperciva
445161748Scperciva# Perform sanity checks and set some final parameters
446161748Scperciva# in preparation for fetching files.  Figure out which
447161748Scperciva# set of updates should be downloaded: If the user is
448161748Scperciva# running *-p[0-9]+, strip off the last part; if the
449161748Scperciva# user is running -SECURITY, call it -RELEASE.  Chdir
450161748Scperciva# into the working directory.
451161748Scpercivafetch_check_params () {
452161748Scperciva	export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
453161748Scperciva
454161748Scperciva	_SERVERNAME_z=\
455161748Scperciva"SERVERNAME must be given via command line or configuration file."
456161748Scperciva	_KEYPRINT_z="Key must be given via -k option or configuration file."
457161748Scperciva	_KEYPRINT_bad="Invalid key fingerprint: "
458161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
459161748Scperciva
460161748Scperciva	if [ -z "${SERVERNAME}" ]; then
461161748Scperciva		echo -n "`basename $0`: "
462161748Scperciva		echo "${_SERVERNAME_z}"
463161748Scperciva		exit 1
464161748Scperciva	fi
465161748Scperciva	if [ -z "${KEYPRINT}" ]; then
466161748Scperciva		echo -n "`basename $0`: "
467161748Scperciva		echo "${_KEYPRINT_z}"
468161748Scperciva		exit 1
469161748Scperciva	fi
470161748Scperciva	if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
471161748Scperciva		echo -n "`basename $0`: "
472161748Scperciva		echo -n "${_KEYPRINT_bad}"
473161748Scperciva		echo ${KEYPRINT}
474161748Scperciva		exit 1
475161748Scperciva	fi
476161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
477161748Scperciva		echo -n "`basename $0`: "
478161748Scperciva		echo -n "${_WORKDIR_bad}"
479161748Scperciva		echo ${WORKDIR}
480161748Scperciva		exit 1
481161748Scperciva	fi
482161748Scperciva	cd ${WORKDIR} || exit 1
483161748Scperciva
484161748Scperciva	# Generate release number.  The s/SECURITY/RELEASE/ bit exists
485161748Scperciva	# to provide an upgrade path for FreeBSD Update 1.x users, since
486161748Scperciva	# the kernels provided by FreeBSD Update 1.x are always labelled
487161748Scperciva	# as X.Y-SECURITY.
488161748Scperciva	RELNUM=`uname -r |
489161748Scperciva	    sed -E 's,-p[0-9]+,,' |
490161748Scperciva	    sed -E 's,-SECURITY,-RELEASE,'`
491161748Scperciva	ARCH=`uname -m`
492161748Scperciva	FETCHDIR=${RELNUM}/${ARCH}
493161748Scperciva
494161748Scperciva	# Figure out what directory contains the running kernel
495161748Scperciva	BOOTFILE=`sysctl -n kern.bootfile`
496161748Scperciva	KERNELDIR=${BOOTFILE%/kernel}
497161748Scperciva	if ! [ -d ${KERNELDIR} ]; then
498161748Scperciva		echo "Cannot identify running kernel"
499161748Scperciva		exit 1
500161748Scperciva	fi
501161748Scperciva
502167189Scperciva	# Figure out what kernel configuration is running.  We start with
503167189Scperciva	# the output of `uname -i`, and then make the following adjustments:
504167189Scperciva	# 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
505167189Scperciva	# file says "ident SMP-GENERIC", I don't know...
506167189Scperciva	# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
507167189Scperciva	# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
508167189Scperciva	# we're running an SMP kernel.  This mis-identification is a bug
509167189Scperciva	# which was fixed in 6.2-STABLE.
510167189Scperciva	KERNCONF=`uname -i`
511167189Scperciva	if [ ${KERNCONF} = "SMP-GENERIC" ]; then
512167189Scperciva		KERNCONF=SMP
513167189Scperciva	fi
514167189Scperciva	if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
515167189Scperciva		if sysctl kern.version | grep -qE '/SMP$'; then
516167189Scperciva			KERNCONF=SMP
517167189Scperciva		fi
518167189Scperciva	fi
519167189Scperciva
520161748Scperciva	# Define some paths
521161748Scperciva	BSPATCH=/usr/bin/bspatch
522161748Scperciva	SHA256=/sbin/sha256
523161748Scperciva	PHTTPGET=/usr/libexec/phttpget
524161748Scperciva
525161748Scperciva	# Set up variables relating to VERBOSELEVEL
526161748Scperciva	fetch_setup_verboselevel
527161748Scperciva
528161748Scperciva	# Construct a unique name from ${BASEDIR}
529161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
530161748Scperciva}
531161748Scperciva
532161748Scperciva# Perform sanity checks and set some final parameters in
533161748Scperciva# preparation for installing updates.
534161748Scpercivainstall_check_params () {
535161748Scperciva	# Check that we are root.  All sorts of things won't work otherwise.
536161748Scperciva	if [ `id -u` != 0 ]; then
537161748Scperciva		echo "You must be root to run this."
538161748Scperciva		exit 1
539161748Scperciva	fi
540161748Scperciva
541161748Scperciva	# Check that we have a working directory
542161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
543161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
544161748Scperciva		echo -n "`basename $0`: "
545161748Scperciva		echo -n "${_WORKDIR_bad}"
546161748Scperciva		echo ${WORKDIR}
547161748Scperciva		exit 1
548161748Scperciva	fi
549161748Scperciva	cd ${WORKDIR} || exit 1
550161748Scperciva
551161748Scperciva	# Construct a unique name from ${BASEDIR}
552161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
553161748Scperciva
554161748Scperciva	# Check that we have updates ready to install
555161748Scperciva	if ! [ -L ${BDHASH}-install ]; then
556161748Scperciva		echo "No updates are available to install."
557161748Scperciva		echo "Run '$0 fetch' first."
558161748Scperciva		exit 1
559161748Scperciva	fi
560161748Scperciva	if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
561161748Scperciva	    ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
562161748Scperciva		echo "Update manifest is corrupt -- this should never happen."
563161748Scperciva		echo "Re-run '$0 fetch'."
564161748Scperciva		exit 1
565161748Scperciva	fi
566161748Scperciva}
567161748Scperciva
568161748Scperciva# Perform sanity checks and set some final parameters in
569161748Scperciva# preparation for UNinstalling updates.
570161748Scpercivarollback_check_params () {
571161748Scperciva	# Check that we are root.  All sorts of things won't work otherwise.
572161748Scperciva	if [ `id -u` != 0 ]; then
573161748Scperciva		echo "You must be root to run this."
574161748Scperciva		exit 1
575161748Scperciva	fi
576161748Scperciva
577161748Scperciva	# Check that we have a working directory
578161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
579161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
580161748Scperciva		echo -n "`basename $0`: "
581161748Scperciva		echo -n "${_WORKDIR_bad}"
582161748Scperciva		echo ${WORKDIR}
583161748Scperciva		exit 1
584161748Scperciva	fi
585161748Scperciva	cd ${WORKDIR} || exit 1
586161748Scperciva
587161748Scperciva	# Construct a unique name from ${BASEDIR}
588161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
589161748Scperciva
590161748Scperciva	# Check that we have updates ready to rollback
591161748Scperciva	if ! [ -L ${BDHASH}-rollback ]; then
592161748Scperciva		echo "No rollback directory found."
593161748Scperciva		exit 1
594161748Scperciva	fi
595161748Scperciva	if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
596161748Scperciva	    ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
597161748Scperciva		echo "Update manifest is corrupt -- this should never happen."
598161748Scperciva		exit 1
599161748Scperciva	fi
600161748Scperciva}
601161748Scperciva
602161748Scperciva#### Core functionality -- the actual work gets done here
603161748Scperciva
604161748Scperciva# Use an SRV query to pick a server.  If the SRV query doesn't provide
605161748Scperciva# a useful answer, use the server name specified by the user.
606161748Scperciva# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
607161748Scperciva# from that; or if no servers are returned, use ${SERVERNAME}.
608161748Scperciva# This allows a user to specify "portsnap.freebsd.org" (in which case
609161748Scperciva# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
610161748Scperciva# (in which case portsnap will use that particular server, since there
611161748Scperciva# won't be an SRV entry for that name).
612161748Scperciva#
613161748Scperciva# We ignore the Port field, since we are always going to use port 80.
614161748Scperciva
615161748Scperciva# Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
616161748Scperciva# no mirrors are available for any reason.
617161748Scpercivafetch_pick_server_init () {
618161748Scperciva	: > serverlist_tried
619161748Scperciva
620161748Scperciva# Check that host(1) exists (i.e., that the system wasn't built with the
621161748Scperciva# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
622161748Scperciva	if ! which -s host; then
623161748Scperciva		: > serverlist_full
624161748Scperciva		return 1
625161748Scperciva	fi
626161748Scperciva
627161748Scperciva	echo -n "Looking up ${SERVERNAME} mirrors... "
628161748Scperciva
629161748Scperciva# Issue the SRV query and pull out the Priority, Weight, and Target fields.
630161748Scperciva# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
631161748Scperciva# "$name server selection ..."; we allow either format.
632161748Scperciva	MLIST="_http._tcp.${SERVERNAME}"
633161748Scperciva	host -t srv "${MLIST}" |
634161748Scperciva	    sed -nE "s/${MLIST} (has SRV record|server selection) //p" |
635161748Scperciva	    cut -f 1,2,4 -d ' ' |
636161748Scperciva	    sed -e 's/\.$//' |
637161748Scperciva	    sort > serverlist_full
638161748Scperciva
639161748Scperciva# If no records, give up -- we'll just use the server name we were given.
640161748Scperciva	if [ `wc -l < serverlist_full` -eq 0 ]; then
641161748Scperciva		echo "none found."
642161748Scperciva		return 1
643161748Scperciva	fi
644161748Scperciva
645161748Scperciva# Report how many mirrors we found.
646161748Scperciva	echo `wc -l < serverlist_full` "mirrors found."
647161748Scperciva
648161748Scperciva# Generate a random seed for use in picking mirrors.  If HTTP_PROXY
649161748Scperciva# is set, this will be used to generate the seed; otherwise, the seed
650161748Scperciva# will be random.
651161748Scperciva	if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
652161748Scperciva		RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
653161748Scperciva		    tr -d 'a-f' |
654161748Scperciva		    cut -c 1-9`
655161748Scperciva	else
656161748Scperciva		RANDVALUE=`jot -r 1 0 999999999`
657161748Scperciva	fi
658161748Scperciva}
659161748Scperciva
660161748Scperciva# Pick a mirror.  Returns 1 if we have run out of mirrors to try.
661161748Scpercivafetch_pick_server () {
662161748Scperciva# Generate a list of not-yet-tried mirrors
663161748Scperciva	sort serverlist_tried |
664161748Scperciva	    comm -23 serverlist_full - > serverlist
665161748Scperciva
666161748Scperciva# Have we run out of mirrors?
667161748Scperciva	if [ `wc -l < serverlist` -eq 0 ]; then
668161748Scperciva		echo "No mirrors remaining, giving up."
669161748Scperciva		return 1
670161748Scperciva	fi
671161748Scperciva
672161748Scperciva# Find the highest priority level (lowest numeric value).
673161748Scperciva	SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
674161748Scperciva
675161748Scperciva# Add up the weights of the response lines at that priority level.
676161748Scperciva	SRV_WSUM=0;
677161748Scperciva	while read X; do
678161748Scperciva		case "$X" in
679161748Scperciva		${SRV_PRIORITY}\ *)
680161748Scperciva			SRV_W=`echo $X | cut -f 2 -d ' '`
681161748Scperciva			SRV_WSUM=$(($SRV_WSUM + $SRV_W))
682161748Scperciva			;;
683161748Scperciva		esac
684161748Scperciva	done < serverlist
685161748Scperciva
686161748Scperciva# If all the weights are 0, pretend that they are all 1 instead.
687161748Scperciva	if [ ${SRV_WSUM} -eq 0 ]; then
688161748Scperciva		SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
689161748Scperciva		SRV_W_ADD=1
690161748Scperciva	else
691161748Scperciva		SRV_W_ADD=0
692161748Scperciva	fi
693161748Scperciva
694161748Scperciva# Pick a value between 0 and the sum of the weights - 1
695161748Scperciva	SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
696161748Scperciva
697161748Scperciva# Read through the list of mirrors and set SERVERNAME.  Write the line
698161748Scperciva# corresponding to the mirror we selected into serverlist_tried so that
699161748Scperciva# we won't try it again.
700161748Scperciva	while read X; do
701161748Scperciva		case "$X" in
702161748Scperciva		${SRV_PRIORITY}\ *)
703161748Scperciva			SRV_W=`echo $X | cut -f 2 -d ' '`
704161748Scperciva			SRV_W=$(($SRV_W + $SRV_W_ADD))
705161748Scperciva			if [ $SRV_RND -lt $SRV_W ]; then
706161748Scperciva				SERVERNAME=`echo $X | cut -f 3 -d ' '`
707161748Scperciva				echo "$X" >> serverlist_tried
708161748Scperciva				break
709161748Scperciva			else
710161748Scperciva				SRV_RND=$(($SRV_RND - $SRV_W))
711161748Scperciva			fi
712161748Scperciva			;;
713161748Scperciva		esac
714161748Scperciva	done < serverlist
715161748Scperciva}
716161748Scperciva
717161748Scperciva# Take a list of ${oldhash}|${newhash} and output a list of needed patches,
718161748Scperciva# i.e., those for which we have ${oldhash} and don't have ${newhash}.
719161748Scpercivafetch_make_patchlist () {
720161748Scperciva	grep -vE "^([0-9a-f]{64})\|\1$" |
721161748Scperciva	    tr '|' ' ' |
722161748Scperciva		while read X Y; do
723161748Scperciva			if [ -f "files/${Y}.gz" ] ||
724161748Scperciva			    [ ! -f "files/${X}.gz" ]; then
725161748Scperciva				continue
726161748Scperciva			fi
727161748Scperciva			echo "${X}|${Y}"
728161748Scperciva		done | uniq
729161748Scperciva}
730161748Scperciva
731161748Scperciva# Print user-friendly progress statistics
732161748Scpercivafetch_progress () {
733161748Scperciva	LNC=0
734161748Scperciva	while read x; do
735161748Scperciva		LNC=$(($LNC + 1))
736161748Scperciva		if [ $(($LNC % 10)) = 0 ]; then
737161748Scperciva			echo -n $LNC
738161748Scperciva		elif [ $(($LNC % 2)) = 0 ]; then
739161748Scperciva			echo -n .
740161748Scperciva		fi
741161748Scperciva	done
742161748Scperciva	echo -n " "
743161748Scperciva}
744161748Scperciva
745161748Scperciva# Initialize the working directory
746161748Scpercivaworkdir_init () {
747161748Scperciva	mkdir -p files
748161748Scperciva	touch tINDEX.present
749161748Scperciva}
750161748Scperciva
751161748Scperciva# Check that we have a public key with an appropriate hash, or
752161748Scperciva# fetch the key if it doesn't exist.  Returns 1 if the key has
753161748Scperciva# not yet been fetched.
754161748Scpercivafetch_key () {
755161748Scperciva	if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
756161748Scperciva		return 0
757161748Scperciva	fi
758161748Scperciva
759161748Scperciva	echo -n "Fetching public key from ${SERVERNAME}... "
760161748Scperciva	rm -f pub.ssl
761161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
762161748Scperciva	    2>${QUIETREDIR} || true
763161748Scperciva	if ! [ -r pub.ssl ]; then
764161748Scperciva		echo "failed."
765161748Scperciva		return 1
766161748Scperciva	fi
767161748Scperciva	if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
768161748Scperciva		echo "key has incorrect hash."
769161748Scperciva		rm -f pub.ssl
770161748Scperciva		return 1
771161748Scperciva	fi
772161748Scperciva	echo "done."
773161748Scperciva}
774161748Scperciva
775161748Scperciva# Fetch metadata signature, aka "tag".
776161748Scpercivafetch_tag () {
777161748Scperciva	echo ${NDEBUG} "Fetching metadata signature from ${SERVERNAME}... "
778161748Scperciva	rm -f latest.ssl
779161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl	\
780161748Scperciva	    2>${QUIETREDIR} || true
781161748Scperciva	if ! [ -r latest.ssl ]; then
782161748Scperciva		echo "failed."
783161748Scperciva		return 1
784161748Scperciva	fi
785161748Scperciva
786161748Scperciva	openssl rsautl -pubin -inkey pub.ssl -verify		\
787161748Scperciva	    < latest.ssl > tag.new 2>${QUIETREDIR} || true
788161748Scperciva	rm latest.ssl
789161748Scperciva
790161748Scperciva	if ! [ `wc -l < tag.new` = 1 ] ||
791161748Scperciva	    ! grep -qE	\
792161748Scperciva    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
793161748Scperciva		tag.new; then
794161748Scperciva		echo "invalid signature."
795161748Scperciva		return 1
796161748Scperciva	fi
797161748Scperciva
798161748Scperciva	echo "done."
799161748Scperciva
800161748Scperciva	RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
801161748Scperciva	TINDEXHASH=`cut -f 5 -d '|' < tag.new`
802161748Scperciva	EOLTIME=`cut -f 6 -d '|' < tag.new`
803161748Scperciva}
804161748Scperciva
805161748Scperciva# Sanity-check the patch number in a tag, to make sure that we're not
806161748Scperciva# going to "update" backwards and to prevent replay attacks.
807161748Scpercivafetch_tagsanity () {
808161748Scperciva	# Check that we're not going to move from -pX to -pY with Y < X.
809161748Scperciva	RELPX=`uname -r | sed -E 's,.*-,,'`
810161748Scperciva	if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
811161748Scperciva		RELPX=`echo ${RELPX} | cut -c 2-`
812161748Scperciva	else
813161748Scperciva		RELPX=0
814161748Scperciva	fi
815161748Scperciva	if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
816161748Scperciva		echo
817161748Scperciva		echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
818161748Scperciva		echo " appear older than what"
819161748Scperciva		echo "we are currently running (`uname -r`)!"
820161748Scperciva		echo "Cowardly refusing to proceed any further."
821161748Scperciva		return 1
822161748Scperciva	fi
823161748Scperciva
824161748Scperciva	# If "tag" exists and corresponds to ${RELNUM}, make sure that
825161748Scperciva	# it contains a patch number <= RELPATCHNUM, in order to protect
826161748Scperciva	# against rollback (replay) attacks.
827161748Scperciva	if [ -f tag ] &&
828161748Scperciva	    grep -qE	\
829161748Scperciva    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
830161748Scperciva		tag; then
831161748Scperciva		LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
832161748Scperciva
833161748Scperciva		if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
834161748Scperciva			echo
835161748Scperciva			echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
836161748Scperciva			echo " are older than the"
837161748Scperciva			echo -n "most recently seen updates"
838161748Scperciva			echo " (${RELNUM}-p${LASTRELPATCHNUM})."
839161748Scperciva			echo "Cowardly refusing to proceed any further."
840161748Scperciva			return 1
841161748Scperciva		fi
842161748Scperciva	fi
843161748Scperciva}
844161748Scperciva
845161748Scperciva# Fetch metadata index file
846161748Scpercivafetch_metadata_index () {
847161748Scperciva	echo ${NDEBUG} "Fetching metadata index... "
848161748Scperciva	rm -f ${TINDEXHASH}
849161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
850161748Scperciva	    2>${QUIETREDIR}
851161748Scperciva	if ! [ -f ${TINDEXHASH} ]; then
852161748Scperciva		echo "failed."
853161748Scperciva		return 1
854161748Scperciva	fi
855161748Scperciva	if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
856161748Scperciva		echo "update metadata index corrupt."
857161748Scperciva		return 1
858161748Scperciva	fi
859161748Scperciva	echo "done."
860161748Scperciva}
861161748Scperciva
862161748Scperciva# Print an error message about signed metadata being bogus.
863161748Scpercivafetch_metadata_bogus () {
864161748Scperciva	echo
865161748Scperciva	echo "The update metadata$1 is correctly signed, but"
866161748Scperciva	echo "failed an integrity check."
867161748Scperciva	echo "Cowardly refusing to proceed any further."
868161748Scperciva	return 1
869161748Scperciva}
870161748Scperciva
871161748Scperciva# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
872161748Scperciva# with the lines not named in $@ from tINDEX.present (if that file exists).
873161748Scpercivafetch_metadata_index_merge () {
874161748Scperciva	for METAFILE in $@; do
875161748Scperciva		if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l`	\
876161748Scperciva		    -ne 1 ]; then
877161748Scperciva			fetch_metadata_bogus " index"
878161748Scperciva			return 1
879161748Scperciva		fi
880161748Scperciva
881161748Scperciva		grep -E "${METAFILE}\|" ${TINDEXHASH}
882161748Scperciva	done |
883161748Scperciva	    sort > tINDEX.wanted
884161748Scperciva
885161748Scperciva	if [ -f tINDEX.present ]; then
886161748Scperciva		join -t '|' -v 2 tINDEX.wanted tINDEX.present |
887161748Scperciva		    sort -m - tINDEX.wanted > tINDEX.new
888161748Scperciva		rm tINDEX.wanted
889161748Scperciva	else
890161748Scperciva		mv tINDEX.wanted tINDEX.new
891161748Scperciva	fi
892161748Scperciva}
893161748Scperciva
894161748Scperciva# Sanity check all the lines of tINDEX.new.  Even if more metadata lines
895161748Scperciva# are added by future versions of the server, this won't cause problems,
896161748Scperciva# since the only lines which appear in tINDEX.new are the ones which we
897161748Scperciva# specifically grepped out of ${TINDEXHASH}.
898161748Scpercivafetch_metadata_index_sanity () {
899161748Scperciva	if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
900161748Scperciva		fetch_metadata_bogus " index"
901161748Scperciva		return 1
902161748Scperciva	fi
903161748Scperciva}
904161748Scperciva
905161748Scperciva# Sanity check the metadata file $1.
906161748Scpercivafetch_metadata_sanity () {
907161748Scperciva	# Some aliases to save space later: ${P} is a character which can
908161748Scperciva	# appear in a path; ${M} is the four numeric metadata fields; and
909161748Scperciva	# ${H} is a sha256 hash.
910161748Scperciva	P="[-+./:=_[[:alnum:]]"
911161748Scperciva	M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
912161748Scperciva	H="[0-9a-f]{64}"
913161748Scperciva
914161748Scperciva	# Check that the first four fields make sense.
915161748Scperciva	if gunzip -c < files/$1.gz |
916161748Scperciva	    grep -qvE "^[a-z]+\|[0-9a-z]+\|${P}+\|[fdL-]\|"; then
917161748Scperciva		fetch_metadata_bogus ""
918161748Scperciva		return 1
919161748Scperciva	fi
920161748Scperciva
921161748Scperciva	# Remove the first three fields.
922161748Scperciva	gunzip -c < files/$1.gz |
923161748Scperciva	    cut -f 4- -d '|' > sanitycheck.tmp
924161748Scperciva
925161748Scperciva	# Sanity check entries with type 'f'
926161748Scperciva	if grep -E '^f' sanitycheck.tmp |
927161748Scperciva	    grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
928161748Scperciva		fetch_metadata_bogus ""
929161748Scperciva		return 1
930161748Scperciva	fi
931161748Scperciva
932161748Scperciva	# Sanity check entries with type 'd'
933161748Scperciva	if grep -E '^d' sanitycheck.tmp |
934161748Scperciva	    grep -qvE "^d\|${M}\|\|\$"; then
935161748Scperciva		fetch_metadata_bogus ""
936161748Scperciva		return 1
937161748Scperciva	fi
938161748Scperciva
939161748Scperciva	# Sanity check entries with type 'L'
940161748Scperciva	if grep -E '^L' sanitycheck.tmp |
941161748Scperciva	    grep -qvE "^L\|${M}\|${P}*\|\$"; then
942161748Scperciva		fetch_metadata_bogus ""
943161748Scperciva		return 1
944161748Scperciva	fi
945161748Scperciva
946161748Scperciva	# Sanity check entries with type '-'
947161748Scperciva	if grep -E '^-' sanitycheck.tmp |
948161748Scperciva	    grep -qvE "^-\|\|\|\|\|\|"; then
949161748Scperciva		fetch_metadata_bogus ""
950161748Scperciva		return 1
951161748Scperciva	fi
952161748Scperciva
953161748Scperciva	# Clean up
954161748Scperciva	rm sanitycheck.tmp
955161748Scperciva}
956161748Scperciva
957161748Scperciva# Fetch the metadata index and metadata files listed in $@,
958161748Scperciva# taking advantage of metadata patches where possible.
959161748Scpercivafetch_metadata () {
960161748Scperciva	fetch_metadata_index || return 1
961161748Scperciva	fetch_metadata_index_merge $@ || return 1
962161748Scperciva	fetch_metadata_index_sanity || return 1
963161748Scperciva
964161748Scperciva	# Generate a list of wanted metadata patches
965161748Scperciva	join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
966161748Scperciva	    fetch_make_patchlist > patchlist
967161748Scperciva
968161748Scperciva	if [ -s patchlist ]; then
969161748Scperciva		# Attempt to fetch metadata patches
970161748Scperciva		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
971161748Scperciva		echo ${NDEBUG} "metadata patches.${DDSTATS}"
972161748Scperciva		tr '|' '-' < patchlist |
973161748Scperciva		    lam -s "${FETCHDIR}/tp/" - -s ".gz" |
974161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
975161748Scperciva			2>${STATSREDIR} | fetch_progress
976161748Scperciva		echo "done."
977161748Scperciva
978161748Scperciva		# Attempt to apply metadata patches
979161748Scperciva		echo -n "Applying metadata patches... "
980161748Scperciva		tr '|' ' ' < patchlist |
981161748Scperciva		    while read X Y; do
982161748Scperciva			if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
983161748Scperciva			gunzip -c < ${X}-${Y}.gz > diff
984161748Scperciva			gunzip -c < files/${X}.gz > diff-OLD
985161748Scperciva
986161748Scperciva			# Figure out which lines are being added and removed
987161748Scperciva			grep -E '^-' diff |
988161748Scperciva			    cut -c 2- |
989161748Scperciva			    while read PREFIX; do
990161748Scperciva				look "${PREFIX}" diff-OLD
991161748Scperciva			    done |
992161748Scperciva			    sort > diff-rm
993161748Scperciva			grep -E '^\+' diff |
994161748Scperciva			    cut -c 2- > diff-add
995161748Scperciva
996161748Scperciva			# Generate the new file
997161748Scperciva			comm -23 diff-OLD diff-rm |
998161748Scperciva			    sort - diff-add > diff-NEW
999161748Scperciva
1000161748Scperciva			if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
1001161748Scperciva				mv diff-NEW files/${Y}
1002161748Scperciva				gzip -n files/${Y}
1003161748Scperciva			else
1004161748Scperciva				mv diff-NEW ${Y}.bad
1005161748Scperciva			fi
1006161748Scperciva			rm -f ${X}-${Y}.gz diff
1007161748Scperciva			rm -f diff-OLD diff-NEW diff-add diff-rm
1008161748Scperciva		done 2>${QUIETREDIR}
1009161748Scperciva		echo "done."
1010161748Scperciva	fi
1011161748Scperciva
1012161748Scperciva	# Update metadata without patches
1013161748Scperciva	cut -f 2 -d '|' < tINDEX.new |
1014161748Scperciva	    while read Y; do
1015161748Scperciva		if [ ! -f "files/${Y}.gz" ]; then
1016161748Scperciva			echo ${Y};
1017161748Scperciva		fi
1018164600Scperciva	    done |
1019164600Scperciva	    sort -u > filelist
1020161748Scperciva
1021161748Scperciva	if [ -s filelist ]; then
1022161748Scperciva		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1023161748Scperciva		echo ${NDEBUG} "metadata files... "
1024161748Scperciva		lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
1025161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1026161748Scperciva		    2>${QUIETREDIR}
1027161748Scperciva
1028161748Scperciva		while read Y; do
1029161748Scperciva			if ! [ -f ${Y}.gz ]; then
1030161748Scperciva				echo "failed."
1031161748Scperciva				return 1
1032161748Scperciva			fi
1033161748Scperciva			if [ `gunzip -c < ${Y}.gz |
1034161748Scperciva			    ${SHA256} -q` = ${Y} ]; then
1035161748Scperciva				mv ${Y}.gz files/${Y}.gz
1036161748Scperciva			else
1037161748Scperciva				echo "metadata is corrupt."
1038161748Scperciva				return 1
1039161748Scperciva			fi
1040161748Scperciva		done < filelist
1041161748Scperciva		echo "done."
1042161748Scperciva	fi
1043161748Scperciva
1044161748Scperciva# Sanity-check the metadata files.
1045161748Scperciva	cut -f 2 -d '|' tINDEX.new > filelist
1046161748Scperciva	while read X; do
1047161748Scperciva		fetch_metadata_sanity ${X} || return 1
1048161748Scperciva	done < filelist
1049161748Scperciva
1050161748Scperciva# Remove files which are no longer needed
1051161748Scperciva	cut -f 2 -d '|' tINDEX.present |
1052161748Scperciva	    sort > oldfiles
1053161748Scperciva	cut -f 2 -d '|' tINDEX.new |
1054161748Scperciva	    sort |
1055161748Scperciva	    comm -13 - oldfiles |
1056161748Scperciva	    lam -s "files/" - -s ".gz" |
1057161748Scperciva	    xargs rm -f
1058161748Scperciva	rm patchlist filelist oldfiles
1059161748Scperciva	rm ${TINDEXHASH}
1060161748Scperciva
1061161748Scperciva# We're done!
1062161748Scperciva	mv tINDEX.new tINDEX.present
1063161748Scperciva	mv tag.new tag
1064161748Scperciva
1065161748Scperciva	return 0
1066161748Scperciva}
1067161748Scperciva
1068161869Scperciva# Generate a filtered version of the metadata file $1 from the downloaded
1069161748Scperciva# file, by fishing out the lines corresponding to components we're trying
1070161748Scperciva# to keep updated, and then removing lines corresponding to paths we want
1071161748Scperciva# to ignore.
1072161748Scpercivafetch_filter_metadata () {
1073161748Scperciva	METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
1074161748Scperciva	gunzip -c < files/${METAHASH}.gz > $1.all
1075161748Scperciva
1076161748Scperciva	# Fish out the lines belonging to components we care about.
1077161748Scperciva	# Canonicalize directory names by removing any trailing / in
1078161748Scperciva	# order to avoid listing directories multiple times if they
1079161748Scperciva	# belong to multiple components.  Turning "/" into "" doesn't
1080161748Scperciva	# matter, since we add a leading "/" when we use paths later.
1081161748Scperciva	for C in ${COMPONENTS}; do
1082161748Scperciva		look "`echo ${C} | tr '/' '|'`|" $1.all
1083161748Scperciva	done |
1084161748Scperciva	    cut -f 3- -d '|' |
1085161748Scperciva	    sed -e 's,/|d|,|d|,' |
1086161748Scperciva	    sort -u > $1.tmp
1087161748Scperciva
1088161748Scperciva	# Figure out which lines to ignore and remove them.
1089161748Scperciva	for X in ${IGNOREPATHS}; do
1090161748Scperciva		grep -E "^${X}" $1.tmp
1091161748Scperciva	done |
1092161748Scperciva	    sort -u |
1093161748Scperciva	    comm -13 - $1.tmp > $1
1094161748Scperciva
1095161748Scperciva	# Remove temporary files.
1096161748Scperciva	rm $1.all $1.tmp
1097161748Scperciva}
1098161748Scperciva
1099167189Scperciva# Filter the metadata file $1 by adding lines with "/boot/${KERNCONF}"
1100164600Scperciva# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
1101167189Scperciva# trailing "/kernel"); and if "/boot/${KERNCONF}" does not exist, remove
1102164600Scperciva# the original lines which start with that.
1103164600Scperciva# Put another way: Deal with the fact that the FOO kernel is sometimes
1104164600Scperciva# installed in /boot/FOO/ and is sometimes installed elsewhere.
1105161748Scpercivafetch_filter_kernel_names () {
1106164600Scperciva
1107164600Scperciva	grep ^/boot/${KERNCONF} $1 |
1108164600Scperciva	    sed -e "s,/boot/${KERNCONF},${KERNELDIR},g" |
1109161748Scperciva	    sort - $1 > $1.tmp
1110161748Scperciva	mv $1.tmp $1
1111164600Scperciva
1112164600Scperciva	if ! [ -d /boot/${KERNCONF} ]; then
1113164600Scperciva		grep -v ^/boot/${KERNCONF} $1 > $1.tmp
1114164600Scperciva		mv $1.tmp $1
1115164600Scperciva	fi
1116161748Scperciva}
1117161748Scperciva
1118161748Scperciva# For all paths appearing in $1 or $3, inspect the system
1119161748Scperciva# and generate $2 describing what is currently installed.
1120161748Scpercivafetch_inspect_system () {
1121161748Scperciva	# No errors yet...
1122161748Scperciva	rm -f .err
1123161748Scperciva
1124161748Scperciva	# Tell the user why his disk is suddenly making lots of noise
1125161748Scperciva	echo -n "Inspecting system... "
1126161748Scperciva
1127161748Scperciva	# Generate list of files to inspect
1128161748Scperciva	cat $1 $3 |
1129161748Scperciva	    cut -f 1 -d '|' |
1130161748Scperciva	    sort -u > filelist
1131161748Scperciva
1132161748Scperciva	# Examine each file and output lines of the form
1133161748Scperciva	# /path/to/file|type|device-inum|user|group|perm|flags|value
1134161748Scperciva	# sorted by device and inode number.
1135161748Scperciva	while read F; do
1136161748Scperciva		# If the symlink/file/directory does not exist, record this.
1137161748Scperciva		if ! [ -e ${BASEDIR}/${F} ]; then
1138161748Scperciva			echo "${F}|-||||||"
1139161748Scperciva			continue
1140161748Scperciva		fi
1141161748Scperciva		if ! [ -r ${BASEDIR}/${F} ]; then
1142161748Scperciva			echo "Cannot read file: ${BASEDIR}/${F}"	\
1143161748Scperciva			    >/dev/stderr
1144161748Scperciva			touch .err
1145161748Scperciva			return 1
1146161748Scperciva		fi
1147161748Scperciva
1148161748Scperciva		# Otherwise, output an index line.
1149161748Scperciva		if [ -L ${BASEDIR}/${F} ]; then
1150161748Scperciva			echo -n "${F}|L|"
1151161748Scperciva			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1152161748Scperciva			readlink ${BASEDIR}/${F};
1153161748Scperciva		elif [ -f ${BASEDIR}/${F} ]; then
1154161748Scperciva			echo -n "${F}|f|"
1155161748Scperciva			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1156161748Scperciva			sha256 -q ${BASEDIR}/${F};
1157161748Scperciva		elif [ -d ${BASEDIR}/${F} ]; then
1158161748Scperciva			echo -n "${F}|d|"
1159161748Scperciva			stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1160161748Scperciva		else
1161161748Scperciva			echo "Unknown file type: ${BASEDIR}/${F}"	\
1162161748Scperciva			    >/dev/stderr
1163161748Scperciva			touch .err
1164161748Scperciva			return 1
1165161748Scperciva		fi
1166161748Scperciva	done < filelist |
1167161748Scperciva	    sort -k 3,3 -t '|' > $2.tmp
1168161748Scperciva	rm filelist
1169161748Scperciva
1170161748Scperciva	# Check if an error occured during system inspection
1171161748Scperciva	if [ -f .err ]; then
1172161748Scperciva		return 1
1173161748Scperciva	fi
1174161748Scperciva
1175161748Scperciva	# Convert to the form
1176161748Scperciva	# /path/to/file|type|user|group|perm|flags|value|hlink
1177161748Scperciva	# by resolving identical device and inode numbers into hard links.
1178161748Scperciva	cut -f 1,3 -d '|' $2.tmp |
1179161748Scperciva	    sort -k 1,1 -t '|' |
1180161748Scperciva	    sort -s -u -k 2,2 -t '|' |
1181161748Scperciva	    join -1 2 -2 3 -t '|' - $2.tmp |
1182161748Scperciva	    awk -F \| -v OFS=\|		\
1183161748Scperciva		'{
1184161748Scperciva		    if (($2 == $3) || ($4 == "-"))
1185161748Scperciva			print $3,$4,$5,$6,$7,$8,$9,""
1186161748Scperciva		    else
1187161748Scperciva			print $3,$4,$5,$6,$7,$8,$9,$2
1188161748Scperciva		}' |
1189161748Scperciva	    sort > $2
1190161748Scperciva	rm $2.tmp
1191161748Scperciva
1192161748Scperciva	# We're finished looking around
1193161748Scperciva	echo "done."
1194161748Scperciva}
1195161748Scperciva
1196161748Scperciva# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
1197161748Scperciva# which correspond to lines in $2 with hashes not matching $1 or $3.  For
1198161748Scperciva# entries in $2 marked "not present" (aka. type -), remove lines from $[123]
1199161748Scperciva# unless there is a corresponding entry in $1.
1200161748Scpercivafetch_filter_unmodified_notpresent () {
1201161748Scperciva	# Figure out which lines of $1 and $3 correspond to bits which
1202161748Scperciva	# should only be updated if they haven't changed, and fish out
1203161748Scperciva	# the (path, type, value) tuples.
1204161748Scperciva	# NOTE: We don't consider a file to be "modified" if it matches
1205161748Scperciva	# the hash from $3.
1206161748Scperciva	for X in ${UPDATEIFUNMODIFIED}; do
1207161748Scperciva		grep -E "^${X}" $1
1208161748Scperciva		grep -E "^${X}" $3
1209161748Scperciva	done |
1210161748Scperciva	    cut -f 1,2,7 -d '|' |
1211161748Scperciva	    sort > $1-values
1212161748Scperciva
1213161748Scperciva	# Do the same for $2.
1214161748Scperciva	for X in ${UPDATEIFUNMODIFIED}; do
1215161748Scperciva		grep -E "^${X}" $2
1216161748Scperciva	done |
1217161748Scperciva	    cut -f 1,2,7 -d '|' |
1218161748Scperciva	    sort > $2-values
1219161748Scperciva
1220161748Scperciva	# Any entry in $2-values which is not in $1-values corresponds to
1221161748Scperciva	# a path which we need to remove from $1, $2, and $3.
1222161748Scperciva	comm -13 $1-values $2-values > mlines
1223161748Scperciva	rm $1-values $2-values
1224161748Scperciva
1225161748Scperciva	# Any lines in $2 which are not in $1 AND are "not present" lines
1226161748Scperciva	# also belong in mlines.
1227161748Scperciva	comm -13 $1 $2 |
1228161748Scperciva	    cut -f 1,2,7 -d '|' |
1229161748Scperciva	    fgrep '|-|' >> mlines
1230161748Scperciva
1231161748Scperciva	# Remove lines from $1, $2, and $3
1232161748Scperciva	for X in $1 $2 $3; do
1233161748Scperciva		sort -t '|' -k 1,1 ${X} > ${X}.tmp
1234161748Scperciva		cut -f 1 -d '|' < mlines |
1235161748Scperciva		    sort |
1236161748Scperciva		    join -v 2 -t '|' - ${X}.tmp |
1237161748Scperciva		    sort > ${X}
1238161748Scperciva		rm ${X}.tmp
1239161748Scperciva	done
1240161748Scperciva
1241161748Scperciva	# Store a list of the modified files, for future reference
1242161748Scperciva	fgrep -v '|-|' mlines |
1243161748Scperciva	    cut -f 1 -d '|' > modifiedfiles
1244161748Scperciva	rm mlines
1245161748Scperciva}
1246161748Scperciva
1247161748Scperciva# For each entry in $1 of type -, remove any corresponding
1248161748Scperciva# entry from $2 if ${ALLOWADD} != "yes".  Remove all entries
1249161748Scperciva# of type - from $1.
1250161748Scpercivafetch_filter_allowadd () {
1251161748Scperciva	cut -f 1,2 -d '|' < $1 |
1252161748Scperciva	    fgrep '|-' |
1253161748Scperciva	    cut -f 1 -d '|' > filesnotpresent
1254161748Scperciva
1255161748Scperciva	if [ ${ALLOWADD} != "yes" ]; then
1256161748Scperciva		sort < $2 |
1257161748Scperciva		    join -v 1 -t '|' - filesnotpresent |
1258161748Scperciva		    sort > $2.tmp
1259161748Scperciva		mv $2.tmp $2
1260161748Scperciva	fi
1261161748Scperciva
1262161748Scperciva	sort < $1 |
1263161748Scperciva	    join -v 1 -t '|' - filesnotpresent |
1264161748Scperciva	    sort > $1.tmp
1265161748Scperciva	mv $1.tmp $1
1266161748Scperciva	rm filesnotpresent
1267161748Scperciva}
1268161748Scperciva
1269161748Scperciva# If ${ALLOWDELETE} != "yes", then remove any entries from $1
1270161748Scperciva# which don't correspond to entries in $2.
1271161748Scpercivafetch_filter_allowdelete () {
1272161748Scperciva	# Produce a lists ${PATH}|${TYPE}
1273161748Scperciva	for X in $1 $2; do
1274161748Scperciva		cut -f 1-2 -d '|' < ${X} |
1275161748Scperciva		    sort -u > ${X}.nodes
1276161748Scperciva	done
1277161748Scperciva
1278161748Scperciva	# Figure out which lines need to be removed from $1.
1279161748Scperciva	if [ ${ALLOWDELETE} != "yes" ]; then
1280161748Scperciva		comm -23 $1.nodes $2.nodes > $1.badnodes
1281161748Scperciva	else
1282161748Scperciva		: > $1.badnodes
1283161748Scperciva	fi
1284161748Scperciva
1285161748Scperciva	# Remove the relevant lines from $1
1286161748Scperciva	while read X; do
1287161748Scperciva		look "${X}|" $1
1288161748Scperciva	done < $1.badnodes |
1289161748Scperciva	    comm -13 - $1 > $1.tmp
1290161748Scperciva	mv $1.tmp $1
1291161748Scperciva
1292161748Scperciva	rm $1.badnodes $1.nodes $2.nodes
1293161748Scperciva}
1294161748Scperciva
1295161748Scperciva# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
1296161748Scperciva# with metadata not matching any entry in $1, replace the corresponding
1297161748Scperciva# line of $3 with one having the same metadata as the entry in $2.
1298161748Scpercivafetch_filter_modified_metadata () {
1299161748Scperciva	# Fish out the metadata from $1 and $2
1300161748Scperciva	for X in $1 $2; do
1301161748Scperciva		cut -f 1-6 -d '|' < ${X} > ${X}.metadata
1302161748Scperciva	done
1303161748Scperciva
1304161748Scperciva	# Find the metadata we need to keep
1305161748Scperciva	if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
1306161748Scperciva		comm -13 $1.metadata $2.metadata > keepmeta
1307161748Scperciva	else
1308161748Scperciva		: > keepmeta
1309161748Scperciva	fi
1310161748Scperciva
1311161748Scperciva	# Extract the lines which we need to remove from $3, and
1312161748Scperciva	# construct the lines which we need to add to $3.
1313161748Scperciva	: > $3.remove
1314161748Scperciva	: > $3.add
1315161748Scperciva	while read LINE; do
1316161748Scperciva		NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
1317161748Scperciva		look "${NODE}|" $3 >> $3.remove
1318161748Scperciva		look "${NODE}|" $3 |
1319161748Scperciva		    cut -f 7- -d '|' |
1320161748Scperciva		    lam -s "${LINE}|" - >> $3.add
1321161748Scperciva	done < keepmeta
1322161748Scperciva
1323161748Scperciva	# Remove the specified lines and add the new lines.
1324161748Scperciva	sort $3.remove |
1325161748Scperciva	    comm -13 - $3 |
1326161748Scperciva	    sort -u - $3.add > $3.tmp
1327161748Scperciva	mv $3.tmp $3
1328161748Scperciva
1329161748Scperciva	rm keepmeta $1.metadata $2.metadata $3.add $3.remove
1330161748Scperciva}
1331161748Scperciva
1332161748Scperciva# Remove lines from $1 and $2 which are identical;
1333161748Scperciva# no need to update a file if it isn't changing.
1334161748Scpercivafetch_filter_uptodate () {
1335161748Scperciva	comm -23 $1 $2 > $1.tmp
1336161748Scperciva	comm -13 $1 $2 > $2.tmp
1337161748Scperciva
1338161748Scperciva	mv $1.tmp $1
1339161748Scperciva	mv $2.tmp $2
1340161748Scperciva}
1341161748Scperciva
1342161748Scperciva# Prepare to fetch files: Generate a list of the files we need,
1343161748Scperciva# copy the unmodified files we have into /files/, and generate
1344161748Scperciva# a list of patches to download.
1345161748Scpercivafetch_files_prepare () {
1346161748Scperciva	# Tell the user why his disk is suddenly making lots of noise
1347161748Scperciva	echo -n "Preparing to download files... "
1348161748Scperciva
1349161748Scperciva	# Reduce indices to ${PATH}|${HASH} pairs
1350161748Scperciva	for X in $1 $2 $3; do
1351161748Scperciva		cut -f 1,2,7 -d '|' < ${X} |
1352161748Scperciva		    fgrep '|f|' |
1353161748Scperciva		    cut -f 1,3 -d '|' |
1354161748Scperciva		    sort > ${X}.hashes
1355161748Scperciva	done
1356161748Scperciva
1357161748Scperciva	# List of files wanted
1358161748Scperciva	cut -f 2 -d '|' < $3.hashes |
1359161748Scperciva	    sort -u > files.wanted
1360161748Scperciva
1361161748Scperciva	# Generate a list of unmodified files
1362161748Scperciva	comm -12 $1.hashes $2.hashes |
1363161748Scperciva	    sort -k 1,1 -t '|' > unmodified.files
1364161748Scperciva
1365161748Scperciva	# Copy all files into /files/.  We only need the unmodified files
1366161748Scperciva	# for use in patching; but we'll want all of them if the user asks
1367161748Scperciva	# to rollback the updates later.
1368161748Scperciva	cut -f 1 -d '|' < $2.hashes |
1369161748Scperciva	    while read F; do
1370161748Scperciva		cp "${BASEDIR}/${F}" tmpfile
1371161748Scperciva		gzip -c < tmpfile > files/`sha256 -q tmpfile`.gz
1372161748Scperciva		rm tmpfile
1373161748Scperciva	    done
1374161748Scperciva
1375161748Scperciva	# Produce a list of patches to download
1376161748Scperciva	sort -k 1,1 -t '|' $3.hashes |
1377161748Scperciva	    join -t '|' -o 2.2,1.2 - unmodified.files |
1378161748Scperciva	    fetch_make_patchlist > patchlist
1379161748Scperciva
1380161748Scperciva	# Garbage collect
1381161748Scperciva	rm unmodified.files $1.hashes $2.hashes $3.hashes
1382161748Scperciva
1383161748Scperciva	# We don't need the list of possible old files any more.
1384161748Scperciva	rm $1
1385161748Scperciva
1386161748Scperciva	# We're finished making noise
1387161748Scperciva	echo "done."
1388161748Scperciva}
1389161748Scperciva
1390161748Scperciva# Fetch files.
1391161748Scpercivafetch_files () {
1392161748Scperciva	# Attempt to fetch patches
1393161748Scperciva	if [ -s patchlist ]; then
1394161748Scperciva		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1395161748Scperciva		echo ${NDEBUG} "patches.${DDSTATS}"
1396161748Scperciva		tr '|' '-' < patchlist |
1397161748Scperciva		    lam -s "${FETCHDIR}/bp/" - |
1398161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1399161748Scperciva			2>${STATSREDIR} | fetch_progress
1400161748Scperciva		echo "done."
1401161748Scperciva
1402161748Scperciva		# Attempt to apply patches
1403161748Scperciva		echo -n "Applying patches... "
1404161748Scperciva		tr '|' ' ' < patchlist |
1405161748Scperciva		    while read X Y; do
1406161748Scperciva			if [ ! -f "${X}-${Y}" ]; then continue; fi
1407161748Scperciva			gunzip -c < files/${X}.gz > OLD
1408161748Scperciva
1409161748Scperciva			bspatch OLD NEW ${X}-${Y}
1410161748Scperciva
1411161748Scperciva			if [ `${SHA256} -q NEW` = ${Y} ]; then
1412161748Scperciva				mv NEW files/${Y}
1413161748Scperciva				gzip -n files/${Y}
1414161748Scperciva			fi
1415161748Scperciva			rm -f diff OLD NEW ${X}-${Y}
1416161748Scperciva		done 2>${QUIETREDIR}
1417161748Scperciva		echo "done."
1418161748Scperciva	fi
1419161748Scperciva
1420161748Scperciva	# Download files which couldn't be generate via patching
1421161748Scperciva	while read Y; do
1422161748Scperciva		if [ ! -f "files/${Y}.gz" ]; then
1423161748Scperciva			echo ${Y};
1424161748Scperciva		fi
1425161748Scperciva	done < files.wanted > filelist
1426161748Scperciva
1427161748Scperciva	if [ -s filelist ]; then
1428161748Scperciva		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1429161748Scperciva		echo ${NDEBUG} "files... "
1430161748Scperciva		lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
1431161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1432161748Scperciva		    2>${QUIETREDIR}
1433161748Scperciva
1434161748Scperciva		while read Y; do
1435161748Scperciva			if ! [ -f ${Y}.gz ]; then
1436161748Scperciva				echo "failed."
1437161748Scperciva				return 1
1438161748Scperciva			fi
1439161748Scperciva			if [ `gunzip -c < ${Y}.gz |
1440161748Scperciva			    ${SHA256} -q` = ${Y} ]; then
1441161748Scperciva				mv ${Y}.gz files/${Y}.gz
1442161748Scperciva			else
1443161748Scperciva				echo "${Y} has incorrect hash."
1444161748Scperciva				return 1
1445161748Scperciva			fi
1446161748Scperciva		done < filelist
1447161748Scperciva		echo "done."
1448161748Scperciva	fi
1449161748Scperciva
1450161748Scperciva	# Clean up
1451161748Scperciva	rm files.wanted filelist patchlist
1452161748Scperciva}
1453161748Scperciva
1454161748Scperciva# Create and populate install manifest directory; and report what updates
1455161748Scperciva# are available.
1456161748Scpercivafetch_create_manifest () {
1457161748Scperciva	# If we have an existing install manifest, nuke it.
1458161748Scperciva	if [ -L "${BDHASH}-install" ]; then
1459161748Scperciva		rm -r ${BDHASH}-install/
1460161748Scperciva		rm ${BDHASH}-install
1461161748Scperciva	fi
1462161748Scperciva
1463161748Scperciva	# Report to the user if any updates were avoided due to local changes
1464161748Scperciva	if [ -s modifiedfiles ]; then
1465161748Scperciva		echo
1466161748Scperciva		echo -n "The following files are affected by updates, "
1467161748Scperciva		echo "but no changes have"
1468161748Scperciva		echo -n "been downloaded because the files have been "
1469161748Scperciva		echo "modified locally:"
1470161748Scperciva		cat modifiedfiles
1471161748Scperciva	fi
1472161748Scperciva	rm modifiedfiles
1473161748Scperciva
1474161748Scperciva	# If no files will be updated, tell the user and exit
1475161748Scperciva	if ! [ -s INDEX-PRESENT ] &&
1476161748Scperciva	    ! [ -s INDEX-NEW ]; then
1477161748Scperciva		rm INDEX-PRESENT INDEX-NEW
1478161748Scperciva		echo
1479161748Scperciva		echo -n "No updates needed to update system to "
1480161748Scperciva		echo "${RELNUM}-p${RELPATCHNUM}."
1481161748Scperciva		return
1482161748Scperciva	fi
1483161748Scperciva
1484161748Scperciva	# Divide files into (a) removed files, (b) added files, and
1485161748Scperciva	# (c) updated files.
1486161748Scperciva	cut -f 1 -d '|' < INDEX-PRESENT |
1487161748Scperciva	    sort > INDEX-PRESENT.flist
1488161748Scperciva	cut -f 1 -d '|' < INDEX-NEW |
1489161748Scperciva	    sort > INDEX-NEW.flist
1490161748Scperciva	comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
1491161748Scperciva	comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
1492161748Scperciva	comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
1493161748Scperciva	rm INDEX-PRESENT.flist INDEX-NEW.flist
1494161748Scperciva
1495161748Scperciva	# Report removed files, if any
1496161748Scperciva	if [ -s files.removed ]; then
1497161748Scperciva		echo
1498161748Scperciva		echo -n "The following files will be removed "
1499161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1500161748Scperciva		cat files.removed
1501161748Scperciva	fi
1502161748Scperciva	rm files.removed
1503161748Scperciva
1504161748Scperciva	# Report added files, if any
1505161748Scperciva	if [ -s files.added ]; then
1506161748Scperciva		echo
1507161748Scperciva		echo -n "The following files will be added "
1508161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1509161748Scperciva		cat files.added
1510161748Scperciva	fi
1511161748Scperciva	rm files.added
1512161748Scperciva
1513161748Scperciva	# Report updated files, if any
1514161748Scperciva	if [ -s files.updated ]; then
1515161748Scperciva		echo
1516161748Scperciva		echo -n "The following files will be updated "
1517161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1518161748Scperciva
1519161748Scperciva		cat files.updated
1520161748Scperciva	fi
1521161748Scperciva	rm files.updated
1522161748Scperciva
1523161748Scperciva	# Create a directory for the install manifest.
1524161748Scperciva	MDIR=`mktemp -d install.XXXXXX` || return 1
1525161748Scperciva
1526161748Scperciva	# Populate it
1527161748Scperciva	mv INDEX-PRESENT ${MDIR}/INDEX-OLD
1528161748Scperciva	mv INDEX-NEW ${MDIR}/INDEX-NEW
1529161748Scperciva
1530161748Scperciva	# Link it into place
1531161748Scperciva	ln -s ${MDIR} ${BDHASH}-install
1532161748Scperciva}
1533161748Scperciva
1534161748Scperciva# Warn about any upcoming EoL
1535161748Scpercivafetch_warn_eol () {
1536161748Scperciva	# What's the current time?
1537161748Scperciva	NOWTIME=`date "+%s"`
1538161748Scperciva
1539161748Scperciva	# When did we last warn about the EoL date?
1540161748Scperciva	if [ -f lasteolwarn ]; then
1541161748Scperciva		LASTWARN=`cat lasteolwarn`
1542161748Scperciva	else
1543161748Scperciva		LASTWARN=`expr ${NOWTIME} - 63072000`
1544161748Scperciva	fi
1545161748Scperciva
1546161748Scperciva	# If the EoL time is past, warn.
1547161748Scperciva	if [ ${EOLTIME} -lt ${NOWTIME} ]; then
1548161748Scperciva		echo
1549161748Scperciva		cat <<-EOF
1550161869Scperciva		WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
1551161748Scperciva		Any security issues discovered after `date -r ${EOLTIME}`
1552161748Scperciva		will not have been corrected.
1553161748Scperciva		EOF
1554161748Scperciva		return 1
1555161748Scperciva	fi
1556161748Scperciva
1557161748Scperciva	# Figure out how long it has been since we last warned about the
1558161748Scperciva	# upcoming EoL, and how much longer we have left.
1559161748Scperciva	SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
1560161748Scperciva	TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
1561161748Scperciva
1562161748Scperciva	# Don't warn if the EoL is more than 6 months away
1563161748Scperciva	if [ ${TIMELEFT} -gt 15768000 ]; then
1564161748Scperciva		return 0
1565161748Scperciva	fi
1566161748Scperciva
1567161748Scperciva	# Don't warn if the time remaining is more than 3 times the time
1568161748Scperciva	# since the last warning.
1569161748Scperciva	if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
1570161748Scperciva		return 0
1571161748Scperciva	fi
1572161748Scperciva
1573161748Scperciva	# Figure out what time units to use.
1574161748Scperciva	if [ ${TIMELEFT} -lt 604800 ]; then
1575161748Scperciva		UNIT="day"
1576161748Scperciva		SIZE=86400
1577161748Scperciva	elif [ ${TIMELEFT} -lt 2678400 ]; then
1578161748Scperciva		UNIT="week"
1579161748Scperciva		SIZE=604800
1580161748Scperciva	else
1581161748Scperciva		UNIT="month"
1582161748Scperciva		SIZE=2678400
1583161748Scperciva	fi
1584161748Scperciva
1585161748Scperciva	# Compute the right number of units
1586161748Scperciva	NUM=`expr ${TIMELEFT} / ${SIZE}`
1587161748Scperciva	if [ ${NUM} != 1 ]; then
1588161748Scperciva		UNIT="${UNIT}s"
1589161748Scperciva	fi
1590161748Scperciva
1591161748Scperciva	# Print the warning
1592161748Scperciva	echo
1593161748Scperciva	cat <<-EOF
1594161748Scperciva		WARNING: `uname -sr` is approaching its End-of-Life date.
1595161748Scperciva		It is strongly recommended that you upgrade to a newer
1596161748Scperciva		release within the next ${NUM} ${UNIT}.
1597161748Scperciva	EOF
1598161748Scperciva
1599161748Scperciva	# Update the stored time of last warning
1600161748Scperciva	echo ${NOWTIME} > lasteolwarn
1601161748Scperciva}
1602161748Scperciva
1603161748Scperciva# Do the actual work involved in "fetch" / "cron".
1604161748Scpercivafetch_run () {
1605161748Scperciva	workdir_init || return 1
1606161748Scperciva
1607161748Scperciva	# Prepare the mirror list.
1608161748Scperciva	fetch_pick_server_init && fetch_pick_server
1609161748Scperciva
1610161748Scperciva	# Try to fetch the public key until we run out of servers.
1611161748Scperciva	while ! fetch_key; do
1612161748Scperciva		fetch_pick_server || return 1
1613161748Scperciva	done
1614161748Scperciva
1615161748Scperciva	# Try to fetch the metadata index signature ("tag") until we run
1616161748Scperciva	# out of available servers; and sanity check the downloaded tag.
1617161748Scperciva	while ! fetch_tag; do
1618161748Scperciva		fetch_pick_server || return 1
1619161748Scperciva	done
1620161748Scperciva	fetch_tagsanity || return 1
1621161748Scperciva
1622161748Scperciva	# Fetch the latest INDEX-NEW and INDEX-OLD files.
1623161748Scperciva	fetch_metadata INDEX-NEW INDEX-OLD || return 1
1624161748Scperciva
1625161748Scperciva	# Generate filtered INDEX-NEW and INDEX-OLD files containing only
1626161748Scperciva	# the lines which (a) belong to components we care about, and (b)
1627161748Scperciva	# don't correspond to paths we're explicitly ignoring.
1628161748Scperciva	fetch_filter_metadata INDEX-NEW || return 1
1629161748Scperciva	fetch_filter_metadata INDEX-OLD || return 1
1630161748Scperciva
1631161748Scperciva	# Translate /boot/`uname -i` into ${KERNELDIR}
1632161748Scperciva	fetch_filter_kernel_names INDEX-NEW
1633161748Scperciva	fetch_filter_kernel_names INDEX-OLD
1634161748Scperciva
1635161748Scperciva	# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
1636161748Scperciva	# system and generate an INDEX-PRESENT file.
1637161748Scperciva	fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
1638161748Scperciva
1639161748Scperciva	# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
1640161748Scperciva	# correspond to lines in INDEX-PRESENT with hashes not appearing
1641161748Scperciva	# in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
1642161748Scperciva	# INDEX-PRESENT has type - and there isn't a corresponding entry in
1643161748Scperciva	# INDEX-OLD with type -.
1644161748Scperciva	fetch_filter_unmodified_notpresent INDEX-OLD INDEX-PRESENT INDEX-NEW
1645161748Scperciva
1646161748Scperciva	# For each entry in INDEX-PRESENT of type -, remove any corresponding
1647161748Scperciva	# entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
1648161748Scperciva	# of type - from INDEX-PRESENT.
1649161748Scperciva	fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
1650161748Scperciva
1651161748Scperciva	# If ${ALLOWDELETE} != "yes", then remove any entries from
1652161748Scperciva	# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
1653161748Scperciva	fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
1654161748Scperciva
1655161748Scperciva	# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
1656161748Scperciva	# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
1657161748Scperciva	# replace the corresponding line of INDEX-NEW with one having the
1658161748Scperciva	# same metadata as the entry in INDEX-PRESENT.
1659161748Scperciva	fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
1660161748Scperciva
1661161748Scperciva	# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
1662161748Scperciva	# no need to update a file if it isn't changing.
1663161748Scperciva	fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
1664161748Scperciva
1665161748Scperciva	# Prepare to fetch files: Generate a list of the files we need,
1666161748Scperciva	# copy the unmodified files we have into /files/, and generate
1667161748Scperciva	# a list of patches to download.
1668161748Scperciva	fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW
1669161748Scperciva
1670161748Scperciva	# Fetch files.
1671161748Scperciva	fetch_files || return 1
1672161748Scperciva
1673161748Scperciva	# Create and populate install manifest directory; and report what
1674161748Scperciva	# updates are available.
1675161748Scperciva	fetch_create_manifest || return 1
1676161748Scperciva
1677161748Scperciva	# Warn about any upcoming EoL
1678161748Scperciva	fetch_warn_eol || return 1
1679161748Scperciva}
1680161748Scperciva
1681161748Scperciva# Make sure that all the file hashes mentioned in $@ have corresponding
1682161748Scperciva# gzipped files stored in /files/.
1683161748Scpercivainstall_verify () {
1684161748Scperciva	# Generate a list of hashes
1685161748Scperciva	cat $@ |
1686161748Scperciva	    cut -f 2,7 -d '|' |
1687161748Scperciva	    grep -E '^f' |
1688161748Scperciva	    cut -f 2 -d '|' |
1689161748Scperciva	    sort -u > filelist
1690161748Scperciva
1691161748Scperciva	# Make sure all the hashes exist
1692161748Scperciva	while read HASH; do
1693161748Scperciva		if ! [ -f files/${HASH}.gz ]; then
1694161748Scperciva			echo -n "Update files missing -- "
1695161748Scperciva			echo "this should never happen."
1696161748Scperciva			echo "Re-run '$0 fetch'."
1697161748Scperciva			return 1
1698161748Scperciva		fi
1699161748Scperciva	done < filelist
1700161748Scperciva
1701161748Scperciva	# Clean up
1702161748Scperciva	rm filelist
1703161748Scperciva}
1704161748Scperciva
1705161748Scperciva# Remove the system immutable flag from files
1706161748Scpercivainstall_unschg () {
1707161748Scperciva	# Generate file list
1708161748Scperciva	cat $@ |
1709161748Scperciva	    cut -f 1 -d '|' > filelist
1710161748Scperciva
1711161748Scperciva	# Remove flags
1712161748Scperciva	while read F; do
1713161748Scperciva		if ! [ -e ${F} ]; then
1714161748Scperciva			continue
1715161748Scperciva		fi
1716161748Scperciva
1717161748Scperciva		chflags noschg ${F} || return 1
1718161748Scperciva	done < filelist
1719161748Scperciva
1720161748Scperciva	# Clean up
1721161748Scperciva	rm filelist
1722161748Scperciva}
1723161748Scperciva
1724161748Scperciva# Install new files
1725161748Scpercivainstall_from_index () {
1726161748Scperciva	# First pass: Do everything apart from setting file flags.  We
1727161748Scperciva	# can't set flags yet, because schg inhibits hard linking.
1728161748Scperciva	sort -k 1,1 -t '|' $1 |
1729161748Scperciva	    tr '|' ' ' |
1730161748Scperciva	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
1731161748Scperciva		case ${TYPE} in
1732161748Scperciva		d)
1733161748Scperciva			# Create a directory
1734161748Scperciva			install -d -o ${OWNER} -g ${GROUP}		\
1735161748Scperciva			    -m ${PERM} ${BASEDIR}/${FPATH}
1736161748Scperciva			;;
1737161748Scperciva		f)
1738161748Scperciva			if [ -z "${LINK}" ]; then
1739161748Scperciva				# Create a file, without setting flags.
1740161748Scperciva				gunzip < files/${HASH}.gz > ${HASH}
1741161748Scperciva				install -S -o ${OWNER} -g ${GROUP}	\
1742161748Scperciva				    -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
1743161748Scperciva				rm ${HASH}
1744161748Scperciva			else
1745161748Scperciva				# Create a hard link.
1746161748Scperciva				ln -f ${LINK} ${BASEDIR}/${FPATH}
1747161748Scperciva			fi
1748161748Scperciva			;;
1749161748Scperciva		L)
1750161748Scperciva			# Create a symlink
1751161748Scperciva			ln -sfh ${HASH} ${BASEDIR}/${FPATH}
1752161748Scperciva			;;
1753161748Scperciva		esac
1754161748Scperciva	    done
1755161748Scperciva
1756161748Scperciva	# Perform a second pass, adding file flags.
1757161748Scperciva	tr '|' ' ' < $1 |
1758161748Scperciva	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
1759161748Scperciva		if [ ${TYPE} = "f" ] &&
1760161748Scperciva		    ! [ ${FLAGS} = "0" ]; then
1761161748Scperciva			chflags ${FLAGS} ${BASEDIR}/${FPATH}
1762161748Scperciva		fi
1763161748Scperciva	    done
1764161748Scperciva}
1765161748Scperciva
1766161748Scperciva# Remove files which we want to delete
1767161748Scpercivainstall_delete () {
1768161748Scperciva	# Generate list of new files
1769161748Scperciva	cut -f 1 -d '|' < $2 |
1770161748Scperciva	    sort > newfiles
1771161748Scperciva
1772161748Scperciva	# Generate subindex of old files we want to nuke
1773161748Scperciva	sort -k 1,1 -t '|' $1 |
1774161748Scperciva	    join -t '|' -v 1 - newfiles |
1775164600Scperciva	    sort -r -k 1,1 -t '|' |
1776161748Scperciva	    cut -f 1,2 -d '|' |
1777161748Scperciva	    tr '|' ' ' > killfiles
1778161748Scperciva
1779161748Scperciva	# Remove the offending bits
1780161748Scperciva	while read FPATH TYPE; do
1781161748Scperciva		case ${TYPE} in
1782161748Scperciva		d)
1783161748Scperciva			rmdir ${BASEDIR}/${FPATH}
1784161748Scperciva			;;
1785161748Scperciva		f)
1786161748Scperciva			rm ${BASEDIR}/${FPATH}
1787161748Scperciva			;;
1788161748Scperciva		L)
1789161748Scperciva			rm ${BASEDIR}/${FPATH}
1790161748Scperciva			;;
1791161748Scperciva		esac
1792161748Scperciva	done < killfiles
1793161748Scperciva
1794161748Scperciva	# Clean up
1795161748Scperciva	rm newfiles killfiles
1796161748Scperciva}
1797161748Scperciva
1798161748Scperciva# Update linker.hints if anything in /boot/ was touched
1799161748Scpercivainstall_kldxref () {
1800161748Scperciva	if cat $@ |
1801161748Scperciva	    grep -qE '^/boot/'; then
1802161748Scperciva		kldxref -R /boot/
1803161748Scperciva	fi
1804161748Scperciva}
1805161748Scperciva
1806161748Scperciva# Rearrange bits to allow the installed updates to be rolled back
1807161748Scpercivainstall_setup_rollback () {
1808161748Scperciva	if [ -L ${BDHASH}-rollback ]; then
1809161748Scperciva		mv ${BDHASH}-rollback ${BDHASH}-install/rollback
1810161748Scperciva	fi
1811161748Scperciva
1812161748Scperciva	mv ${BDHASH}-install ${BDHASH}-rollback
1813161748Scperciva}
1814161748Scperciva
1815161748Scperciva# Actually install updates
1816161748Scpercivainstall_run () {
1817161748Scperciva	echo -n "Installing updates..."
1818161748Scperciva
1819161748Scperciva	# Make sure we have all the files we should have
1820161748Scperciva	install_verify ${BDHASH}-install/INDEX-OLD	\
1821161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
1822161748Scperciva
1823161748Scperciva	# Remove system immutable flag from files
1824161748Scperciva	install_unschg ${BDHASH}-install/INDEX-OLD	\
1825161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
1826161748Scperciva
1827161748Scperciva	# Install new files
1828161748Scperciva	install_from_index				\
1829161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
1830161748Scperciva
1831161748Scperciva	# Remove files which we want to delete
1832161748Scperciva	install_delete ${BDHASH}-install/INDEX-OLD	\
1833161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
1834161748Scperciva
1835161748Scperciva	# Update linker.hints if anything in /boot/ was touched
1836161748Scperciva	install_kldxref ${BDHASH}-install/INDEX-OLD	\
1837161748Scperciva	    ${BDHASH}-install/INDEX-NEW
1838161748Scperciva
1839161748Scperciva	# Rearrange bits to allow the installed updates to be rolled back
1840161748Scperciva	install_setup_rollback
1841161748Scperciva
1842161748Scperciva	echo " done."
1843161748Scperciva}
1844161748Scperciva
1845161748Scperciva# Rearrange bits to allow the previous set of updates to be rolled back next.
1846161748Scpercivarollback_setup_rollback () {
1847161748Scperciva	if [ -L ${BDHASH}-rollback/rollback ]; then
1848161748Scperciva		mv ${BDHASH}-rollback/rollback rollback-tmp
1849161748Scperciva		rm -r ${BDHASH}-rollback/
1850161748Scperciva		rm ${BDHASH}-rollback
1851161748Scperciva		mv rollback-tmp ${BDHASH}-rollback
1852161748Scperciva	else
1853161748Scperciva		rm -r ${BDHASH}-rollback/
1854161748Scperciva		rm ${BDHASH}-rollback
1855161748Scperciva	fi
1856161748Scperciva}
1857161748Scperciva
1858161748Scperciva# Actually rollback updates
1859161748Scpercivarollback_run () {
1860161748Scperciva	echo -n "Uninstalling updates..."
1861161748Scperciva
1862161748Scperciva	# If there are updates waiting to be installed, remove them; we
1863161748Scperciva	# want the user to re-run 'fetch' after rolling back updates.
1864161748Scperciva	if [ -L ${BDHASH}-install ]; then
1865161748Scperciva		rm -r ${BDHASH}-install/
1866161748Scperciva		rm ${BDHASH}-install
1867161748Scperciva	fi
1868161748Scperciva
1869161748Scperciva	# Make sure we have all the files we should have
1870161748Scperciva	install_verify ${BDHASH}-rollback/INDEX-NEW	\
1871161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
1872161748Scperciva
1873161748Scperciva	# Remove system immutable flag from files
1874161748Scperciva	install_unschg ${BDHASH}-rollback/INDEX-NEW	\
1875161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
1876161748Scperciva
1877161748Scperciva	# Install new files
1878161748Scperciva	install_from_index				\
1879161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
1880161748Scperciva
1881161748Scperciva	# Remove files which we want to delete
1882161748Scperciva	install_delete ${BDHASH}-rollback/INDEX-NEW	\
1883161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
1884161748Scperciva
1885161748Scperciva	# Update linker.hints if anything in /boot/ was touched
1886161748Scperciva	install_kldxref ${BDHASH}-rollback/INDEX-NEW	\
1887161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD
1888161748Scperciva
1889161748Scperciva	# Remove the rollback directory and the symlink pointing to it; and
1890161748Scperciva	# rearrange bits to allow the previous set of updates to be rolled
1891161748Scperciva	# back next.
1892161748Scperciva	rollback_setup_rollback
1893161748Scperciva
1894161748Scperciva	echo " done."
1895161748Scperciva}
1896161748Scperciva
1897161748Scperciva#### Main functions -- call parameter-handling and core functions
1898161748Scperciva
1899161748Scperciva# Using the command line, configuration file, and defaults,
1900161748Scperciva# set all the parameters which are needed later.
1901161748Scpercivaget_params () {
1902161748Scperciva	init_params
1903161748Scperciva	parse_cmdline $@
1904161748Scperciva	parse_conffile
1905161748Scperciva	default_params
1906161748Scperciva}
1907161748Scperciva
1908161748Scperciva# Fetch command.  Make sure that we're being called
1909161748Scperciva# interactively, then run fetch_check_params and fetch_run
1910161748Scpercivacmd_fetch () {
1911161748Scperciva	if [ ! -t 0 ]; then
1912161748Scperciva		echo -n "`basename $0` fetch should not "
1913161748Scperciva		echo "be run non-interactively."
1914161748Scperciva		echo "Run `basename $0` cron instead."
1915161748Scperciva		exit 1
1916161748Scperciva	fi
1917161748Scperciva	fetch_check_params
1918161748Scperciva	fetch_run || exit 1
1919161748Scperciva}
1920161748Scperciva
1921161748Scperciva# Cron command.  Make sure the parameters are sensible; wait
1922161748Scperciva# rand(3600) seconds; then fetch updates.  While fetching updates,
1923161748Scperciva# send output to a temporary file; only print that file if the
1924161748Scperciva# fetching failed.
1925161748Scpercivacmd_cron () {
1926161748Scperciva	fetch_check_params
1927161748Scperciva	sleep `jot -r 1 0 3600`
1928161748Scperciva
1929161748Scperciva	TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
1930161748Scperciva	if ! fetch_run >> ${TMPFILE} ||
1931161748Scperciva	    ! grep -q "No updates needed" ${TMPFILE} ||
1932161748Scperciva	    [ ${VERBOSELEVEL} = "debug" ]; then
1933161748Scperciva		mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
1934161748Scperciva	fi
1935161748Scperciva
1936161748Scperciva	rm ${TMPFILE}
1937161748Scperciva}
1938161748Scperciva
1939161748Scperciva# Install downloaded updates.
1940161748Scpercivacmd_install () {
1941161748Scperciva	install_check_params
1942161748Scperciva	install_run || exit 1
1943161748Scperciva}
1944161748Scperciva
1945161748Scperciva# Rollback most recently installed updates.
1946161748Scpercivacmd_rollback () {
1947161748Scperciva	rollback_check_params
1948161748Scperciva	rollback_run || exit 1
1949161748Scperciva}
1950161748Scperciva
1951161748Scperciva#### Entry point
1952161748Scperciva
1953161748Scperciva# Make sure we find utilities from the base system
1954161748Scpercivaexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
1955161748Scperciva
1956163564Scperciva# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
1957163564Scpercivaexport LC_ALL=C
1958163564Scperciva
1959161748Scpercivaget_params $@
1960161748Scpercivafor COMMAND in ${COMMANDS}; do
1961161748Scperciva	cmd_${COMMAND}
1962161748Scpercivadone
1963