freebsd-update.sh revision 164600
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 164600 2006-11-25 07:30:12Z 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 502161748Scperciva # Define some paths 503161748Scperciva BSPATCH=/usr/bin/bspatch 504161748Scperciva SHA256=/sbin/sha256 505161748Scperciva PHTTPGET=/usr/libexec/phttpget 506161748Scperciva 507161748Scperciva # Set up variables relating to VERBOSELEVEL 508161748Scperciva fetch_setup_verboselevel 509161748Scperciva 510161748Scperciva # Construct a unique name from ${BASEDIR} 511161748Scperciva BDHASH=`echo ${BASEDIR} | sha256 -q` 512161748Scperciva} 513161748Scperciva 514161748Scperciva# Perform sanity checks and set some final parameters in 515161748Scperciva# preparation for installing updates. 516161748Scpercivainstall_check_params () { 517161748Scperciva # Check that we are root. All sorts of things won't work otherwise. 518161748Scperciva if [ `id -u` != 0 ]; then 519161748Scperciva echo "You must be root to run this." 520161748Scperciva exit 1 521161748Scperciva fi 522161748Scperciva 523161748Scperciva # Check that we have a working directory 524161748Scperciva _WORKDIR_bad="Directory does not exist or is not writable: " 525161748Scperciva if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 526161748Scperciva echo -n "`basename $0`: " 527161748Scperciva echo -n "${_WORKDIR_bad}" 528161748Scperciva echo ${WORKDIR} 529161748Scperciva exit 1 530161748Scperciva fi 531161748Scperciva cd ${WORKDIR} || exit 1 532161748Scperciva 533161748Scperciva # Construct a unique name from ${BASEDIR} 534161748Scperciva BDHASH=`echo ${BASEDIR} | sha256 -q` 535161748Scperciva 536161748Scperciva # Check that we have updates ready to install 537161748Scperciva if ! [ -L ${BDHASH}-install ]; then 538161748Scperciva echo "No updates are available to install." 539161748Scperciva echo "Run '$0 fetch' first." 540161748Scperciva exit 1 541161748Scperciva fi 542161748Scperciva if ! [ -f ${BDHASH}-install/INDEX-OLD ] || 543161748Scperciva ! [ -f ${BDHASH}-install/INDEX-NEW ]; then 544161748Scperciva echo "Update manifest is corrupt -- this should never happen." 545161748Scperciva echo "Re-run '$0 fetch'." 546161748Scperciva exit 1 547161748Scperciva fi 548161748Scperciva} 549161748Scperciva 550161748Scperciva# Perform sanity checks and set some final parameters in 551161748Scperciva# preparation for UNinstalling updates. 552161748Scpercivarollback_check_params () { 553161748Scperciva # Check that we are root. All sorts of things won't work otherwise. 554161748Scperciva if [ `id -u` != 0 ]; then 555161748Scperciva echo "You must be root to run this." 556161748Scperciva exit 1 557161748Scperciva fi 558161748Scperciva 559161748Scperciva # Check that we have a working directory 560161748Scperciva _WORKDIR_bad="Directory does not exist or is not writable: " 561161748Scperciva if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 562161748Scperciva echo -n "`basename $0`: " 563161748Scperciva echo -n "${_WORKDIR_bad}" 564161748Scperciva echo ${WORKDIR} 565161748Scperciva exit 1 566161748Scperciva fi 567161748Scperciva cd ${WORKDIR} || exit 1 568161748Scperciva 569161748Scperciva # Construct a unique name from ${BASEDIR} 570161748Scperciva BDHASH=`echo ${BASEDIR} | sha256 -q` 571161748Scperciva 572161748Scperciva # Check that we have updates ready to rollback 573161748Scperciva if ! [ -L ${BDHASH}-rollback ]; then 574161748Scperciva echo "No rollback directory found." 575161748Scperciva exit 1 576161748Scperciva fi 577161748Scperciva if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] || 578161748Scperciva ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then 579161748Scperciva echo "Update manifest is corrupt -- this should never happen." 580161748Scperciva exit 1 581161748Scperciva fi 582161748Scperciva} 583161748Scperciva 584161748Scperciva#### Core functionality -- the actual work gets done here 585161748Scperciva 586161748Scperciva# Use an SRV query to pick a server. If the SRV query doesn't provide 587161748Scperciva# a useful answer, use the server name specified by the user. 588161748Scperciva# Put another way... look up _http._tcp.${SERVERNAME} and pick a server 589161748Scperciva# from that; or if no servers are returned, use ${SERVERNAME}. 590161748Scperciva# This allows a user to specify "portsnap.freebsd.org" (in which case 591161748Scperciva# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org" 592161748Scperciva# (in which case portsnap will use that particular server, since there 593161748Scperciva# won't be an SRV entry for that name). 594161748Scperciva# 595161748Scperciva# We ignore the Port field, since we are always going to use port 80. 596161748Scperciva 597161748Scperciva# Fetch the mirror list, but do not pick a mirror yet. Returns 1 if 598161748Scperciva# no mirrors are available for any reason. 599161748Scpercivafetch_pick_server_init () { 600161748Scperciva : > serverlist_tried 601161748Scperciva 602161748Scperciva# Check that host(1) exists (i.e., that the system wasn't built with the 603161748Scperciva# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist. 604161748Scperciva if ! which -s host; then 605161748Scperciva : > serverlist_full 606161748Scperciva return 1 607161748Scperciva fi 608161748Scperciva 609161748Scperciva echo -n "Looking up ${SERVERNAME} mirrors... " 610161748Scperciva 611161748Scperciva# Issue the SRV query and pull out the Priority, Weight, and Target fields. 612161748Scperciva# BIND 9 prints "$name has SRV record ..." while BIND 8 prints 613161748Scperciva# "$name server selection ..."; we allow either format. 614161748Scperciva MLIST="_http._tcp.${SERVERNAME}" 615161748Scperciva host -t srv "${MLIST}" | 616161748Scperciva sed -nE "s/${MLIST} (has SRV record|server selection) //p" | 617161748Scperciva cut -f 1,2,4 -d ' ' | 618161748Scperciva sed -e 's/\.$//' | 619161748Scperciva sort > serverlist_full 620161748Scperciva 621161748Scperciva# If no records, give up -- we'll just use the server name we were given. 622161748Scperciva if [ `wc -l < serverlist_full` -eq 0 ]; then 623161748Scperciva echo "none found." 624161748Scperciva return 1 625161748Scperciva fi 626161748Scperciva 627161748Scperciva# Report how many mirrors we found. 628161748Scperciva echo `wc -l < serverlist_full` "mirrors found." 629161748Scperciva 630161748Scperciva# Generate a random seed for use in picking mirrors. If HTTP_PROXY 631161748Scperciva# is set, this will be used to generate the seed; otherwise, the seed 632161748Scperciva# will be random. 633161748Scperciva if [ -n "${HTTP_PROXY}${http_proxy}" ]; then 634161748Scperciva RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" | 635161748Scperciva tr -d 'a-f' | 636161748Scperciva cut -c 1-9` 637161748Scperciva else 638161748Scperciva RANDVALUE=`jot -r 1 0 999999999` 639161748Scperciva fi 640161748Scperciva} 641161748Scperciva 642161748Scperciva# Pick a mirror. Returns 1 if we have run out of mirrors to try. 643161748Scpercivafetch_pick_server () { 644161748Scperciva# Generate a list of not-yet-tried mirrors 645161748Scperciva sort serverlist_tried | 646161748Scperciva comm -23 serverlist_full - > serverlist 647161748Scperciva 648161748Scperciva# Have we run out of mirrors? 649161748Scperciva if [ `wc -l < serverlist` -eq 0 ]; then 650161748Scperciva echo "No mirrors remaining, giving up." 651161748Scperciva return 1 652161748Scperciva fi 653161748Scperciva 654161748Scperciva# Find the highest priority level (lowest numeric value). 655161748Scperciva SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1` 656161748Scperciva 657161748Scperciva# Add up the weights of the response lines at that priority level. 658161748Scperciva SRV_WSUM=0; 659161748Scperciva while read X; do 660161748Scperciva case "$X" in 661161748Scperciva ${SRV_PRIORITY}\ *) 662161748Scperciva SRV_W=`echo $X | cut -f 2 -d ' '` 663161748Scperciva SRV_WSUM=$(($SRV_WSUM + $SRV_W)) 664161748Scperciva ;; 665161748Scperciva esac 666161748Scperciva done < serverlist 667161748Scperciva 668161748Scperciva# If all the weights are 0, pretend that they are all 1 instead. 669161748Scperciva if [ ${SRV_WSUM} -eq 0 ]; then 670161748Scperciva SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l` 671161748Scperciva SRV_W_ADD=1 672161748Scperciva else 673161748Scperciva SRV_W_ADD=0 674161748Scperciva fi 675161748Scperciva 676161748Scperciva# Pick a value between 0 and the sum of the weights - 1 677161748Scperciva SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}` 678161748Scperciva 679161748Scperciva# Read through the list of mirrors and set SERVERNAME. Write the line 680161748Scperciva# corresponding to the mirror we selected into serverlist_tried so that 681161748Scperciva# we won't try it again. 682161748Scperciva while read X; do 683161748Scperciva case "$X" in 684161748Scperciva ${SRV_PRIORITY}\ *) 685161748Scperciva SRV_W=`echo $X | cut -f 2 -d ' '` 686161748Scperciva SRV_W=$(($SRV_W + $SRV_W_ADD)) 687161748Scperciva if [ $SRV_RND -lt $SRV_W ]; then 688161748Scperciva SERVERNAME=`echo $X | cut -f 3 -d ' '` 689161748Scperciva echo "$X" >> serverlist_tried 690161748Scperciva break 691161748Scperciva else 692161748Scperciva SRV_RND=$(($SRV_RND - $SRV_W)) 693161748Scperciva fi 694161748Scperciva ;; 695161748Scperciva esac 696161748Scperciva done < serverlist 697161748Scperciva} 698161748Scperciva 699161748Scperciva# Take a list of ${oldhash}|${newhash} and output a list of needed patches, 700161748Scperciva# i.e., those for which we have ${oldhash} and don't have ${newhash}. 701161748Scpercivafetch_make_patchlist () { 702161748Scperciva grep -vE "^([0-9a-f]{64})\|\1$" | 703161748Scperciva tr '|' ' ' | 704161748Scperciva while read X Y; do 705161748Scperciva if [ -f "files/${Y}.gz" ] || 706161748Scperciva [ ! -f "files/${X}.gz" ]; then 707161748Scperciva continue 708161748Scperciva fi 709161748Scperciva echo "${X}|${Y}" 710161748Scperciva done | uniq 711161748Scperciva} 712161748Scperciva 713161748Scperciva# Print user-friendly progress statistics 714161748Scpercivafetch_progress () { 715161748Scperciva LNC=0 716161748Scperciva while read x; do 717161748Scperciva LNC=$(($LNC + 1)) 718161748Scperciva if [ $(($LNC % 10)) = 0 ]; then 719161748Scperciva echo -n $LNC 720161748Scperciva elif [ $(($LNC % 2)) = 0 ]; then 721161748Scperciva echo -n . 722161748Scperciva fi 723161748Scperciva done 724161748Scperciva echo -n " " 725161748Scperciva} 726161748Scperciva 727161748Scperciva# Initialize the working directory 728161748Scpercivaworkdir_init () { 729161748Scperciva mkdir -p files 730161748Scperciva touch tINDEX.present 731161748Scperciva} 732161748Scperciva 733161748Scperciva# Check that we have a public key with an appropriate hash, or 734161748Scperciva# fetch the key if it doesn't exist. Returns 1 if the key has 735161748Scperciva# not yet been fetched. 736161748Scpercivafetch_key () { 737161748Scperciva if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then 738161748Scperciva return 0 739161748Scperciva fi 740161748Scperciva 741161748Scperciva echo -n "Fetching public key from ${SERVERNAME}... " 742161748Scperciva rm -f pub.ssl 743161748Scperciva fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \ 744161748Scperciva 2>${QUIETREDIR} || true 745161748Scperciva if ! [ -r pub.ssl ]; then 746161748Scperciva echo "failed." 747161748Scperciva return 1 748161748Scperciva fi 749161748Scperciva if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then 750161748Scperciva echo "key has incorrect hash." 751161748Scperciva rm -f pub.ssl 752161748Scperciva return 1 753161748Scperciva fi 754161748Scperciva echo "done." 755161748Scperciva} 756161748Scperciva 757161748Scperciva# Fetch metadata signature, aka "tag". 758161748Scpercivafetch_tag () { 759161748Scperciva echo ${NDEBUG} "Fetching metadata signature from ${SERVERNAME}... " 760161748Scperciva rm -f latest.ssl 761161748Scperciva fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl \ 762161748Scperciva 2>${QUIETREDIR} || true 763161748Scperciva if ! [ -r latest.ssl ]; then 764161748Scperciva echo "failed." 765161748Scperciva return 1 766161748Scperciva fi 767161748Scperciva 768161748Scperciva openssl rsautl -pubin -inkey pub.ssl -verify \ 769161748Scperciva < latest.ssl > tag.new 2>${QUIETREDIR} || true 770161748Scperciva rm latest.ssl 771161748Scperciva 772161748Scperciva if ! [ `wc -l < tag.new` = 1 ] || 773161748Scperciva ! grep -qE \ 774161748Scperciva "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \ 775161748Scperciva tag.new; then 776161748Scperciva echo "invalid signature." 777161748Scperciva return 1 778161748Scperciva fi 779161748Scperciva 780161748Scperciva echo "done." 781161748Scperciva 782161748Scperciva RELPATCHNUM=`cut -f 4 -d '|' < tag.new` 783161748Scperciva TINDEXHASH=`cut -f 5 -d '|' < tag.new` 784161748Scperciva EOLTIME=`cut -f 6 -d '|' < tag.new` 785161748Scperciva} 786161748Scperciva 787161748Scperciva# Sanity-check the patch number in a tag, to make sure that we're not 788161748Scperciva# going to "update" backwards and to prevent replay attacks. 789161748Scpercivafetch_tagsanity () { 790161748Scperciva # Check that we're not going to move from -pX to -pY with Y < X. 791161748Scperciva RELPX=`uname -r | sed -E 's,.*-,,'` 792161748Scperciva if echo ${RELPX} | grep -qE '^p[0-9]+$'; then 793161748Scperciva RELPX=`echo ${RELPX} | cut -c 2-` 794161748Scperciva else 795161748Scperciva RELPX=0 796161748Scperciva fi 797161748Scperciva if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then 798161748Scperciva echo 799161748Scperciva echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})" 800161748Scperciva echo " appear older than what" 801161748Scperciva echo "we are currently running (`uname -r`)!" 802161748Scperciva echo "Cowardly refusing to proceed any further." 803161748Scperciva return 1 804161748Scperciva fi 805161748Scperciva 806161748Scperciva # If "tag" exists and corresponds to ${RELNUM}, make sure that 807161748Scperciva # it contains a patch number <= RELPATCHNUM, in order to protect 808161748Scperciva # against rollback (replay) attacks. 809161748Scperciva if [ -f tag ] && 810161748Scperciva grep -qE \ 811161748Scperciva "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \ 812161748Scperciva tag; then 813161748Scperciva LASTRELPATCHNUM=`cut -f 4 -d '|' < tag` 814161748Scperciva 815161748Scperciva if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then 816161748Scperciva echo 817161748Scperciva echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})" 818161748Scperciva echo " are older than the" 819161748Scperciva echo -n "most recently seen updates" 820161748Scperciva echo " (${RELNUM}-p${LASTRELPATCHNUM})." 821161748Scperciva echo "Cowardly refusing to proceed any further." 822161748Scperciva return 1 823161748Scperciva fi 824161748Scperciva fi 825161748Scperciva} 826161748Scperciva 827161748Scperciva# Fetch metadata index file 828161748Scpercivafetch_metadata_index () { 829161748Scperciva echo ${NDEBUG} "Fetching metadata index... " 830161748Scperciva rm -f ${TINDEXHASH} 831161748Scperciva fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH} 832161748Scperciva 2>${QUIETREDIR} 833161748Scperciva if ! [ -f ${TINDEXHASH} ]; then 834161748Scperciva echo "failed." 835161748Scperciva return 1 836161748Scperciva fi 837161748Scperciva if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then 838161748Scperciva echo "update metadata index corrupt." 839161748Scperciva return 1 840161748Scperciva fi 841161748Scperciva echo "done." 842161748Scperciva} 843161748Scperciva 844161748Scperciva# Print an error message about signed metadata being bogus. 845161748Scpercivafetch_metadata_bogus () { 846161748Scperciva echo 847161748Scperciva echo "The update metadata$1 is correctly signed, but" 848161748Scperciva echo "failed an integrity check." 849161748Scperciva echo "Cowardly refusing to proceed any further." 850161748Scperciva return 1 851161748Scperciva} 852161748Scperciva 853161748Scperciva# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH} 854161748Scperciva# with the lines not named in $@ from tINDEX.present (if that file exists). 855161748Scpercivafetch_metadata_index_merge () { 856161748Scperciva for METAFILE in $@; do 857161748Scperciva if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l` \ 858161748Scperciva -ne 1 ]; then 859161748Scperciva fetch_metadata_bogus " index" 860161748Scperciva return 1 861161748Scperciva fi 862161748Scperciva 863161748Scperciva grep -E "${METAFILE}\|" ${TINDEXHASH} 864161748Scperciva done | 865161748Scperciva sort > tINDEX.wanted 866161748Scperciva 867161748Scperciva if [ -f tINDEX.present ]; then 868161748Scperciva join -t '|' -v 2 tINDEX.wanted tINDEX.present | 869161748Scperciva sort -m - tINDEX.wanted > tINDEX.new 870161748Scperciva rm tINDEX.wanted 871161748Scperciva else 872161748Scperciva mv tINDEX.wanted tINDEX.new 873161748Scperciva fi 874161748Scperciva} 875161748Scperciva 876161748Scperciva# Sanity check all the lines of tINDEX.new. Even if more metadata lines 877161748Scperciva# are added by future versions of the server, this won't cause problems, 878161748Scperciva# since the only lines which appear in tINDEX.new are the ones which we 879161748Scperciva# specifically grepped out of ${TINDEXHASH}. 880161748Scpercivafetch_metadata_index_sanity () { 881161748Scperciva if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then 882161748Scperciva fetch_metadata_bogus " index" 883161748Scperciva return 1 884161748Scperciva fi 885161748Scperciva} 886161748Scperciva 887161748Scperciva# Sanity check the metadata file $1. 888161748Scpercivafetch_metadata_sanity () { 889161748Scperciva # Some aliases to save space later: ${P} is a character which can 890161748Scperciva # appear in a path; ${M} is the four numeric metadata fields; and 891161748Scperciva # ${H} is a sha256 hash. 892161748Scperciva P="[-+./:=_[[:alnum:]]" 893161748Scperciva M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+" 894161748Scperciva H="[0-9a-f]{64}" 895161748Scperciva 896161748Scperciva # Check that the first four fields make sense. 897161748Scperciva if gunzip -c < files/$1.gz | 898161748Scperciva grep -qvE "^[a-z]+\|[0-9a-z]+\|${P}+\|[fdL-]\|"; then 899161748Scperciva fetch_metadata_bogus "" 900161748Scperciva return 1 901161748Scperciva fi 902161748Scperciva 903161748Scperciva # Remove the first three fields. 904161748Scperciva gunzip -c < files/$1.gz | 905161748Scperciva cut -f 4- -d '|' > sanitycheck.tmp 906161748Scperciva 907161748Scperciva # Sanity check entries with type 'f' 908161748Scperciva if grep -E '^f' sanitycheck.tmp | 909161748Scperciva grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then 910161748Scperciva fetch_metadata_bogus "" 911161748Scperciva return 1 912161748Scperciva fi 913161748Scperciva 914161748Scperciva # Sanity check entries with type 'd' 915161748Scperciva if grep -E '^d' sanitycheck.tmp | 916161748Scperciva grep -qvE "^d\|${M}\|\|\$"; then 917161748Scperciva fetch_metadata_bogus "" 918161748Scperciva return 1 919161748Scperciva fi 920161748Scperciva 921161748Scperciva # Sanity check entries with type 'L' 922161748Scperciva if grep -E '^L' sanitycheck.tmp | 923161748Scperciva grep -qvE "^L\|${M}\|${P}*\|\$"; then 924161748Scperciva fetch_metadata_bogus "" 925161748Scperciva return 1 926161748Scperciva fi 927161748Scperciva 928161748Scperciva # Sanity check entries with type '-' 929161748Scperciva if grep -E '^-' sanitycheck.tmp | 930161748Scperciva grep -qvE "^-\|\|\|\|\|\|"; then 931161748Scperciva fetch_metadata_bogus "" 932161748Scperciva return 1 933161748Scperciva fi 934161748Scperciva 935161748Scperciva # Clean up 936161748Scperciva rm sanitycheck.tmp 937161748Scperciva} 938161748Scperciva 939161748Scperciva# Fetch the metadata index and metadata files listed in $@, 940161748Scperciva# taking advantage of metadata patches where possible. 941161748Scpercivafetch_metadata () { 942161748Scperciva fetch_metadata_index || return 1 943161748Scperciva fetch_metadata_index_merge $@ || return 1 944161748Scperciva fetch_metadata_index_sanity || return 1 945161748Scperciva 946161748Scperciva # Generate a list of wanted metadata patches 947161748Scperciva join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new | 948161748Scperciva fetch_make_patchlist > patchlist 949161748Scperciva 950161748Scperciva if [ -s patchlist ]; then 951161748Scperciva # Attempt to fetch metadata patches 952161748Scperciva echo -n "Fetching `wc -l < patchlist | tr -d ' '` " 953161748Scperciva echo ${NDEBUG} "metadata patches.${DDSTATS}" 954161748Scperciva tr '|' '-' < patchlist | 955161748Scperciva lam -s "${FETCHDIR}/tp/" - -s ".gz" | 956161748Scperciva xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 957161748Scperciva 2>${STATSREDIR} | fetch_progress 958161748Scperciva echo "done." 959161748Scperciva 960161748Scperciva # Attempt to apply metadata patches 961161748Scperciva echo -n "Applying metadata patches... " 962161748Scperciva tr '|' ' ' < patchlist | 963161748Scperciva while read X Y; do 964161748Scperciva if [ ! -f "${X}-${Y}.gz" ]; then continue; fi 965161748Scperciva gunzip -c < ${X}-${Y}.gz > diff 966161748Scperciva gunzip -c < files/${X}.gz > diff-OLD 967161748Scperciva 968161748Scperciva # Figure out which lines are being added and removed 969161748Scperciva grep -E '^-' diff | 970161748Scperciva cut -c 2- | 971161748Scperciva while read PREFIX; do 972161748Scperciva look "${PREFIX}" diff-OLD 973161748Scperciva done | 974161748Scperciva sort > diff-rm 975161748Scperciva grep -E '^\+' diff | 976161748Scperciva cut -c 2- > diff-add 977161748Scperciva 978161748Scperciva # Generate the new file 979161748Scperciva comm -23 diff-OLD diff-rm | 980161748Scperciva sort - diff-add > diff-NEW 981161748Scperciva 982161748Scperciva if [ `${SHA256} -q diff-NEW` = ${Y} ]; then 983161748Scperciva mv diff-NEW files/${Y} 984161748Scperciva gzip -n files/${Y} 985161748Scperciva else 986161748Scperciva mv diff-NEW ${Y}.bad 987161748Scperciva fi 988161748Scperciva rm -f ${X}-${Y}.gz diff 989161748Scperciva rm -f diff-OLD diff-NEW diff-add diff-rm 990161748Scperciva done 2>${QUIETREDIR} 991161748Scperciva echo "done." 992161748Scperciva fi 993161748Scperciva 994161748Scperciva # Update metadata without patches 995161748Scperciva cut -f 2 -d '|' < tINDEX.new | 996161748Scperciva while read Y; do 997161748Scperciva if [ ! -f "files/${Y}.gz" ]; then 998161748Scperciva echo ${Y}; 999161748Scperciva fi 1000164600Scperciva done | 1001164600Scperciva sort -u > filelist 1002161748Scperciva 1003161748Scperciva if [ -s filelist ]; then 1004161748Scperciva echo -n "Fetching `wc -l < filelist | tr -d ' '` " 1005161748Scperciva echo ${NDEBUG} "metadata files... " 1006161748Scperciva lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist | 1007161748Scperciva xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1008161748Scperciva 2>${QUIETREDIR} 1009161748Scperciva 1010161748Scperciva while read Y; do 1011161748Scperciva if ! [ -f ${Y}.gz ]; then 1012161748Scperciva echo "failed." 1013161748Scperciva return 1 1014161748Scperciva fi 1015161748Scperciva if [ `gunzip -c < ${Y}.gz | 1016161748Scperciva ${SHA256} -q` = ${Y} ]; then 1017161748Scperciva mv ${Y}.gz files/${Y}.gz 1018161748Scperciva else 1019161748Scperciva echo "metadata is corrupt." 1020161748Scperciva return 1 1021161748Scperciva fi 1022161748Scperciva done < filelist 1023161748Scperciva echo "done." 1024161748Scperciva fi 1025161748Scperciva 1026161748Scperciva# Sanity-check the metadata files. 1027161748Scperciva cut -f 2 -d '|' tINDEX.new > filelist 1028161748Scperciva while read X; do 1029161748Scperciva fetch_metadata_sanity ${X} || return 1 1030161748Scperciva done < filelist 1031161748Scperciva 1032161748Scperciva# Remove files which are no longer needed 1033161748Scperciva cut -f 2 -d '|' tINDEX.present | 1034161748Scperciva sort > oldfiles 1035161748Scperciva cut -f 2 -d '|' tINDEX.new | 1036161748Scperciva sort | 1037161748Scperciva comm -13 - oldfiles | 1038161748Scperciva lam -s "files/" - -s ".gz" | 1039161748Scperciva xargs rm -f 1040161748Scperciva rm patchlist filelist oldfiles 1041161748Scperciva rm ${TINDEXHASH} 1042161748Scperciva 1043161748Scperciva# We're done! 1044161748Scperciva mv tINDEX.new tINDEX.present 1045161748Scperciva mv tag.new tag 1046161748Scperciva 1047161748Scperciva return 0 1048161748Scperciva} 1049161748Scperciva 1050161869Scperciva# Generate a filtered version of the metadata file $1 from the downloaded 1051161748Scperciva# file, by fishing out the lines corresponding to components we're trying 1052161748Scperciva# to keep updated, and then removing lines corresponding to paths we want 1053161748Scperciva# to ignore. 1054161748Scpercivafetch_filter_metadata () { 1055161748Scperciva METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'` 1056161748Scperciva gunzip -c < files/${METAHASH}.gz > $1.all 1057161748Scperciva 1058161748Scperciva # Fish out the lines belonging to components we care about. 1059161748Scperciva # Canonicalize directory names by removing any trailing / in 1060161748Scperciva # order to avoid listing directories multiple times if they 1061161748Scperciva # belong to multiple components. Turning "/" into "" doesn't 1062161748Scperciva # matter, since we add a leading "/" when we use paths later. 1063161748Scperciva for C in ${COMPONENTS}; do 1064161748Scperciva look "`echo ${C} | tr '/' '|'`|" $1.all 1065161748Scperciva done | 1066161748Scperciva cut -f 3- -d '|' | 1067161748Scperciva sed -e 's,/|d|,|d|,' | 1068161748Scperciva sort -u > $1.tmp 1069161748Scperciva 1070161748Scperciva # Figure out which lines to ignore and remove them. 1071161748Scperciva for X in ${IGNOREPATHS}; do 1072161748Scperciva grep -E "^${X}" $1.tmp 1073161748Scperciva done | 1074161748Scperciva sort -u | 1075161748Scperciva comm -13 - $1.tmp > $1 1076161748Scperciva 1077161748Scperciva # Remove temporary files. 1078161748Scperciva rm $1.all $1.tmp 1079161748Scperciva} 1080161748Scperciva 1081164600Scperciva# Filter the metadata file $1 by adding lines with "/boot/`uname -i`" 1082164600Scperciva# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the 1083164600Scperciva# trailing "/kernel"); and if "/boot/`uname -i`" does not exist, remove 1084164600Scperciva# the original lines which start with that. 1085164600Scperciva# Put another way: Deal with the fact that the FOO kernel is sometimes 1086164600Scperciva# installed in /boot/FOO/ and is sometimes installed elsewhere. 1087161748Scpercivafetch_filter_kernel_names () { 1088164600Scperciva KERNCONF=`uname -i` 1089164600Scperciva 1090164600Scperciva grep ^/boot/${KERNCONF} $1 | 1091164600Scperciva sed -e "s,/boot/${KERNCONF},${KERNELDIR},g" | 1092161748Scperciva sort - $1 > $1.tmp 1093161748Scperciva mv $1.tmp $1 1094164600Scperciva 1095164600Scperciva if ! [ -d /boot/${KERNCONF} ]; then 1096164600Scperciva grep -v ^/boot/${KERNCONF} $1 > $1.tmp 1097164600Scperciva mv $1.tmp $1 1098164600Scperciva fi 1099161748Scperciva} 1100161748Scperciva 1101161748Scperciva# For all paths appearing in $1 or $3, inspect the system 1102161748Scperciva# and generate $2 describing what is currently installed. 1103161748Scpercivafetch_inspect_system () { 1104161748Scperciva # No errors yet... 1105161748Scperciva rm -f .err 1106161748Scperciva 1107161748Scperciva # Tell the user why his disk is suddenly making lots of noise 1108161748Scperciva echo -n "Inspecting system... " 1109161748Scperciva 1110161748Scperciva # Generate list of files to inspect 1111161748Scperciva cat $1 $3 | 1112161748Scperciva cut -f 1 -d '|' | 1113161748Scperciva sort -u > filelist 1114161748Scperciva 1115161748Scperciva # Examine each file and output lines of the form 1116161748Scperciva # /path/to/file|type|device-inum|user|group|perm|flags|value 1117161748Scperciva # sorted by device and inode number. 1118161748Scperciva while read F; do 1119161748Scperciva # If the symlink/file/directory does not exist, record this. 1120161748Scperciva if ! [ -e ${BASEDIR}/${F} ]; then 1121161748Scperciva echo "${F}|-||||||" 1122161748Scperciva continue 1123161748Scperciva fi 1124161748Scperciva if ! [ -r ${BASEDIR}/${F} ]; then 1125161748Scperciva echo "Cannot read file: ${BASEDIR}/${F}" \ 1126161748Scperciva >/dev/stderr 1127161748Scperciva touch .err 1128161748Scperciva return 1 1129161748Scperciva fi 1130161748Scperciva 1131161748Scperciva # Otherwise, output an index line. 1132161748Scperciva if [ -L ${BASEDIR}/${F} ]; then 1133161748Scperciva echo -n "${F}|L|" 1134161748Scperciva stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1135161748Scperciva readlink ${BASEDIR}/${F}; 1136161748Scperciva elif [ -f ${BASEDIR}/${F} ]; then 1137161748Scperciva echo -n "${F}|f|" 1138161748Scperciva stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1139161748Scperciva sha256 -q ${BASEDIR}/${F}; 1140161748Scperciva elif [ -d ${BASEDIR}/${F} ]; then 1141161748Scperciva echo -n "${F}|d|" 1142161748Scperciva stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1143161748Scperciva else 1144161748Scperciva echo "Unknown file type: ${BASEDIR}/${F}" \ 1145161748Scperciva >/dev/stderr 1146161748Scperciva touch .err 1147161748Scperciva return 1 1148161748Scperciva fi 1149161748Scperciva done < filelist | 1150161748Scperciva sort -k 3,3 -t '|' > $2.tmp 1151161748Scperciva rm filelist 1152161748Scperciva 1153161748Scperciva # Check if an error occured during system inspection 1154161748Scperciva if [ -f .err ]; then 1155161748Scperciva return 1 1156161748Scperciva fi 1157161748Scperciva 1158161748Scperciva # Convert to the form 1159161748Scperciva # /path/to/file|type|user|group|perm|flags|value|hlink 1160161748Scperciva # by resolving identical device and inode numbers into hard links. 1161161748Scperciva cut -f 1,3 -d '|' $2.tmp | 1162161748Scperciva sort -k 1,1 -t '|' | 1163161748Scperciva sort -s -u -k 2,2 -t '|' | 1164161748Scperciva join -1 2 -2 3 -t '|' - $2.tmp | 1165161748Scperciva awk -F \| -v OFS=\| \ 1166161748Scperciva '{ 1167161748Scperciva if (($2 == $3) || ($4 == "-")) 1168161748Scperciva print $3,$4,$5,$6,$7,$8,$9,"" 1169161748Scperciva else 1170161748Scperciva print $3,$4,$5,$6,$7,$8,$9,$2 1171161748Scperciva }' | 1172161748Scperciva sort > $2 1173161748Scperciva rm $2.tmp 1174161748Scperciva 1175161748Scperciva # We're finished looking around 1176161748Scperciva echo "done." 1177161748Scperciva} 1178161748Scperciva 1179161748Scperciva# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123] 1180161748Scperciva# which correspond to lines in $2 with hashes not matching $1 or $3. For 1181161748Scperciva# entries in $2 marked "not present" (aka. type -), remove lines from $[123] 1182161748Scperciva# unless there is a corresponding entry in $1. 1183161748Scpercivafetch_filter_unmodified_notpresent () { 1184161748Scperciva # Figure out which lines of $1 and $3 correspond to bits which 1185161748Scperciva # should only be updated if they haven't changed, and fish out 1186161748Scperciva # the (path, type, value) tuples. 1187161748Scperciva # NOTE: We don't consider a file to be "modified" if it matches 1188161748Scperciva # the hash from $3. 1189161748Scperciva for X in ${UPDATEIFUNMODIFIED}; do 1190161748Scperciva grep -E "^${X}" $1 1191161748Scperciva grep -E "^${X}" $3 1192161748Scperciva done | 1193161748Scperciva cut -f 1,2,7 -d '|' | 1194161748Scperciva sort > $1-values 1195161748Scperciva 1196161748Scperciva # Do the same for $2. 1197161748Scperciva for X in ${UPDATEIFUNMODIFIED}; do 1198161748Scperciva grep -E "^${X}" $2 1199161748Scperciva done | 1200161748Scperciva cut -f 1,2,7 -d '|' | 1201161748Scperciva sort > $2-values 1202161748Scperciva 1203161748Scperciva # Any entry in $2-values which is not in $1-values corresponds to 1204161748Scperciva # a path which we need to remove from $1, $2, and $3. 1205161748Scperciva comm -13 $1-values $2-values > mlines 1206161748Scperciva rm $1-values $2-values 1207161748Scperciva 1208161748Scperciva # Any lines in $2 which are not in $1 AND are "not present" lines 1209161748Scperciva # also belong in mlines. 1210161748Scperciva comm -13 $1 $2 | 1211161748Scperciva cut -f 1,2,7 -d '|' | 1212161748Scperciva fgrep '|-|' >> mlines 1213161748Scperciva 1214161748Scperciva # Remove lines from $1, $2, and $3 1215161748Scperciva for X in $1 $2 $3; do 1216161748Scperciva sort -t '|' -k 1,1 ${X} > ${X}.tmp 1217161748Scperciva cut -f 1 -d '|' < mlines | 1218161748Scperciva sort | 1219161748Scperciva join -v 2 -t '|' - ${X}.tmp | 1220161748Scperciva sort > ${X} 1221161748Scperciva rm ${X}.tmp 1222161748Scperciva done 1223161748Scperciva 1224161748Scperciva # Store a list of the modified files, for future reference 1225161748Scperciva fgrep -v '|-|' mlines | 1226161748Scperciva cut -f 1 -d '|' > modifiedfiles 1227161748Scperciva rm mlines 1228161748Scperciva} 1229161748Scperciva 1230161748Scperciva# For each entry in $1 of type -, remove any corresponding 1231161748Scperciva# entry from $2 if ${ALLOWADD} != "yes". Remove all entries 1232161748Scperciva# of type - from $1. 1233161748Scpercivafetch_filter_allowadd () { 1234161748Scperciva cut -f 1,2 -d '|' < $1 | 1235161748Scperciva fgrep '|-' | 1236161748Scperciva cut -f 1 -d '|' > filesnotpresent 1237161748Scperciva 1238161748Scperciva if [ ${ALLOWADD} != "yes" ]; then 1239161748Scperciva sort < $2 | 1240161748Scperciva join -v 1 -t '|' - filesnotpresent | 1241161748Scperciva sort > $2.tmp 1242161748Scperciva mv $2.tmp $2 1243161748Scperciva fi 1244161748Scperciva 1245161748Scperciva sort < $1 | 1246161748Scperciva join -v 1 -t '|' - filesnotpresent | 1247161748Scperciva sort > $1.tmp 1248161748Scperciva mv $1.tmp $1 1249161748Scperciva rm filesnotpresent 1250161748Scperciva} 1251161748Scperciva 1252161748Scperciva# If ${ALLOWDELETE} != "yes", then remove any entries from $1 1253161748Scperciva# which don't correspond to entries in $2. 1254161748Scpercivafetch_filter_allowdelete () { 1255161748Scperciva # Produce a lists ${PATH}|${TYPE} 1256161748Scperciva for X in $1 $2; do 1257161748Scperciva cut -f 1-2 -d '|' < ${X} | 1258161748Scperciva sort -u > ${X}.nodes 1259161748Scperciva done 1260161748Scperciva 1261161748Scperciva # Figure out which lines need to be removed from $1. 1262161748Scperciva if [ ${ALLOWDELETE} != "yes" ]; then 1263161748Scperciva comm -23 $1.nodes $2.nodes > $1.badnodes 1264161748Scperciva else 1265161748Scperciva : > $1.badnodes 1266161748Scperciva fi 1267161748Scperciva 1268161748Scperciva # Remove the relevant lines from $1 1269161748Scperciva while read X; do 1270161748Scperciva look "${X}|" $1 1271161748Scperciva done < $1.badnodes | 1272161748Scperciva comm -13 - $1 > $1.tmp 1273161748Scperciva mv $1.tmp $1 1274161748Scperciva 1275161748Scperciva rm $1.badnodes $1.nodes $2.nodes 1276161748Scperciva} 1277161748Scperciva 1278161748Scperciva# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2 1279161748Scperciva# with metadata not matching any entry in $1, replace the corresponding 1280161748Scperciva# line of $3 with one having the same metadata as the entry in $2. 1281161748Scpercivafetch_filter_modified_metadata () { 1282161748Scperciva # Fish out the metadata from $1 and $2 1283161748Scperciva for X in $1 $2; do 1284161748Scperciva cut -f 1-6 -d '|' < ${X} > ${X}.metadata 1285161748Scperciva done 1286161748Scperciva 1287161748Scperciva # Find the metadata we need to keep 1288161748Scperciva if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then 1289161748Scperciva comm -13 $1.metadata $2.metadata > keepmeta 1290161748Scperciva else 1291161748Scperciva : > keepmeta 1292161748Scperciva fi 1293161748Scperciva 1294161748Scperciva # Extract the lines which we need to remove from $3, and 1295161748Scperciva # construct the lines which we need to add to $3. 1296161748Scperciva : > $3.remove 1297161748Scperciva : > $3.add 1298161748Scperciva while read LINE; do 1299161748Scperciva NODE=`echo "${LINE}" | cut -f 1-2 -d '|'` 1300161748Scperciva look "${NODE}|" $3 >> $3.remove 1301161748Scperciva look "${NODE}|" $3 | 1302161748Scperciva cut -f 7- -d '|' | 1303161748Scperciva lam -s "${LINE}|" - >> $3.add 1304161748Scperciva done < keepmeta 1305161748Scperciva 1306161748Scperciva # Remove the specified lines and add the new lines. 1307161748Scperciva sort $3.remove | 1308161748Scperciva comm -13 - $3 | 1309161748Scperciva sort -u - $3.add > $3.tmp 1310161748Scperciva mv $3.tmp $3 1311161748Scperciva 1312161748Scperciva rm keepmeta $1.metadata $2.metadata $3.add $3.remove 1313161748Scperciva} 1314161748Scperciva 1315161748Scperciva# Remove lines from $1 and $2 which are identical; 1316161748Scperciva# no need to update a file if it isn't changing. 1317161748Scpercivafetch_filter_uptodate () { 1318161748Scperciva comm -23 $1 $2 > $1.tmp 1319161748Scperciva comm -13 $1 $2 > $2.tmp 1320161748Scperciva 1321161748Scperciva mv $1.tmp $1 1322161748Scperciva mv $2.tmp $2 1323161748Scperciva} 1324161748Scperciva 1325161748Scperciva# Prepare to fetch files: Generate a list of the files we need, 1326161748Scperciva# copy the unmodified files we have into /files/, and generate 1327161748Scperciva# a list of patches to download. 1328161748Scpercivafetch_files_prepare () { 1329161748Scperciva # Tell the user why his disk is suddenly making lots of noise 1330161748Scperciva echo -n "Preparing to download files... " 1331161748Scperciva 1332161748Scperciva # Reduce indices to ${PATH}|${HASH} pairs 1333161748Scperciva for X in $1 $2 $3; do 1334161748Scperciva cut -f 1,2,7 -d '|' < ${X} | 1335161748Scperciva fgrep '|f|' | 1336161748Scperciva cut -f 1,3 -d '|' | 1337161748Scperciva sort > ${X}.hashes 1338161748Scperciva done 1339161748Scperciva 1340161748Scperciva # List of files wanted 1341161748Scperciva cut -f 2 -d '|' < $3.hashes | 1342161748Scperciva sort -u > files.wanted 1343161748Scperciva 1344161748Scperciva # Generate a list of unmodified files 1345161748Scperciva comm -12 $1.hashes $2.hashes | 1346161748Scperciva sort -k 1,1 -t '|' > unmodified.files 1347161748Scperciva 1348161748Scperciva # Copy all files into /files/. We only need the unmodified files 1349161748Scperciva # for use in patching; but we'll want all of them if the user asks 1350161748Scperciva # to rollback the updates later. 1351161748Scperciva cut -f 1 -d '|' < $2.hashes | 1352161748Scperciva while read F; do 1353161748Scperciva cp "${BASEDIR}/${F}" tmpfile 1354161748Scperciva gzip -c < tmpfile > files/`sha256 -q tmpfile`.gz 1355161748Scperciva rm tmpfile 1356161748Scperciva done 1357161748Scperciva 1358161748Scperciva # Produce a list of patches to download 1359161748Scperciva sort -k 1,1 -t '|' $3.hashes | 1360161748Scperciva join -t '|' -o 2.2,1.2 - unmodified.files | 1361161748Scperciva fetch_make_patchlist > patchlist 1362161748Scperciva 1363161748Scperciva # Garbage collect 1364161748Scperciva rm unmodified.files $1.hashes $2.hashes $3.hashes 1365161748Scperciva 1366161748Scperciva # We don't need the list of possible old files any more. 1367161748Scperciva rm $1 1368161748Scperciva 1369161748Scperciva # We're finished making noise 1370161748Scperciva echo "done." 1371161748Scperciva} 1372161748Scperciva 1373161748Scperciva# Fetch files. 1374161748Scpercivafetch_files () { 1375161748Scperciva # Attempt to fetch patches 1376161748Scperciva if [ -s patchlist ]; then 1377161748Scperciva echo -n "Fetching `wc -l < patchlist | tr -d ' '` " 1378161748Scperciva echo ${NDEBUG} "patches.${DDSTATS}" 1379161748Scperciva tr '|' '-' < patchlist | 1380161748Scperciva lam -s "${FETCHDIR}/bp/" - | 1381161748Scperciva xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1382161748Scperciva 2>${STATSREDIR} | fetch_progress 1383161748Scperciva echo "done." 1384161748Scperciva 1385161748Scperciva # Attempt to apply patches 1386161748Scperciva echo -n "Applying patches... " 1387161748Scperciva tr '|' ' ' < patchlist | 1388161748Scperciva while read X Y; do 1389161748Scperciva if [ ! -f "${X}-${Y}" ]; then continue; fi 1390161748Scperciva gunzip -c < files/${X}.gz > OLD 1391161748Scperciva 1392161748Scperciva bspatch OLD NEW ${X}-${Y} 1393161748Scperciva 1394161748Scperciva if [ `${SHA256} -q NEW` = ${Y} ]; then 1395161748Scperciva mv NEW files/${Y} 1396161748Scperciva gzip -n files/${Y} 1397161748Scperciva fi 1398161748Scperciva rm -f diff OLD NEW ${X}-${Y} 1399161748Scperciva done 2>${QUIETREDIR} 1400161748Scperciva echo "done." 1401161748Scperciva fi 1402161748Scperciva 1403161748Scperciva # Download files which couldn't be generate via patching 1404161748Scperciva while read Y; do 1405161748Scperciva if [ ! -f "files/${Y}.gz" ]; then 1406161748Scperciva echo ${Y}; 1407161748Scperciva fi 1408161748Scperciva done < files.wanted > filelist 1409161748Scperciva 1410161748Scperciva if [ -s filelist ]; then 1411161748Scperciva echo -n "Fetching `wc -l < filelist | tr -d ' '` " 1412161748Scperciva echo ${NDEBUG} "files... " 1413161748Scperciva lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist | 1414161748Scperciva xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1415161748Scperciva 2>${QUIETREDIR} 1416161748Scperciva 1417161748Scperciva while read Y; do 1418161748Scperciva if ! [ -f ${Y}.gz ]; then 1419161748Scperciva echo "failed." 1420161748Scperciva return 1 1421161748Scperciva fi 1422161748Scperciva if [ `gunzip -c < ${Y}.gz | 1423161748Scperciva ${SHA256} -q` = ${Y} ]; then 1424161748Scperciva mv ${Y}.gz files/${Y}.gz 1425161748Scperciva else 1426161748Scperciva echo "${Y} has incorrect hash." 1427161748Scperciva return 1 1428161748Scperciva fi 1429161748Scperciva done < filelist 1430161748Scperciva echo "done." 1431161748Scperciva fi 1432161748Scperciva 1433161748Scperciva # Clean up 1434161748Scperciva rm files.wanted filelist patchlist 1435161748Scperciva} 1436161748Scperciva 1437161748Scperciva# Create and populate install manifest directory; and report what updates 1438161748Scperciva# are available. 1439161748Scpercivafetch_create_manifest () { 1440161748Scperciva # If we have an existing install manifest, nuke it. 1441161748Scperciva if [ -L "${BDHASH}-install" ]; then 1442161748Scperciva rm -r ${BDHASH}-install/ 1443161748Scperciva rm ${BDHASH}-install 1444161748Scperciva fi 1445161748Scperciva 1446161748Scperciva # Report to the user if any updates were avoided due to local changes 1447161748Scperciva if [ -s modifiedfiles ]; then 1448161748Scperciva echo 1449161748Scperciva echo -n "The following files are affected by updates, " 1450161748Scperciva echo "but no changes have" 1451161748Scperciva echo -n "been downloaded because the files have been " 1452161748Scperciva echo "modified locally:" 1453161748Scperciva cat modifiedfiles 1454161748Scperciva fi 1455161748Scperciva rm modifiedfiles 1456161748Scperciva 1457161748Scperciva # If no files will be updated, tell the user and exit 1458161748Scperciva if ! [ -s INDEX-PRESENT ] && 1459161748Scperciva ! [ -s INDEX-NEW ]; then 1460161748Scperciva rm INDEX-PRESENT INDEX-NEW 1461161748Scperciva echo 1462161748Scperciva echo -n "No updates needed to update system to " 1463161748Scperciva echo "${RELNUM}-p${RELPATCHNUM}." 1464161748Scperciva return 1465161748Scperciva fi 1466161748Scperciva 1467161748Scperciva # Divide files into (a) removed files, (b) added files, and 1468161748Scperciva # (c) updated files. 1469161748Scperciva cut -f 1 -d '|' < INDEX-PRESENT | 1470161748Scperciva sort > INDEX-PRESENT.flist 1471161748Scperciva cut -f 1 -d '|' < INDEX-NEW | 1472161748Scperciva sort > INDEX-NEW.flist 1473161748Scperciva comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed 1474161748Scperciva comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added 1475161748Scperciva comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated 1476161748Scperciva rm INDEX-PRESENT.flist INDEX-NEW.flist 1477161748Scperciva 1478161748Scperciva # Report removed files, if any 1479161748Scperciva if [ -s files.removed ]; then 1480161748Scperciva echo 1481161748Scperciva echo -n "The following files will be removed " 1482161748Scperciva echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:" 1483161748Scperciva cat files.removed 1484161748Scperciva fi 1485161748Scperciva rm files.removed 1486161748Scperciva 1487161748Scperciva # Report added files, if any 1488161748Scperciva if [ -s files.added ]; then 1489161748Scperciva echo 1490161748Scperciva echo -n "The following files will be added " 1491161748Scperciva echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:" 1492161748Scperciva cat files.added 1493161748Scperciva fi 1494161748Scperciva rm files.added 1495161748Scperciva 1496161748Scperciva # Report updated files, if any 1497161748Scperciva if [ -s files.updated ]; then 1498161748Scperciva echo 1499161748Scperciva echo -n "The following files will be updated " 1500161748Scperciva echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:" 1501161748Scperciva 1502161748Scperciva cat files.updated 1503161748Scperciva fi 1504161748Scperciva rm files.updated 1505161748Scperciva 1506161748Scperciva # Create a directory for the install manifest. 1507161748Scperciva MDIR=`mktemp -d install.XXXXXX` || return 1 1508161748Scperciva 1509161748Scperciva # Populate it 1510161748Scperciva mv INDEX-PRESENT ${MDIR}/INDEX-OLD 1511161748Scperciva mv INDEX-NEW ${MDIR}/INDEX-NEW 1512161748Scperciva 1513161748Scperciva # Link it into place 1514161748Scperciva ln -s ${MDIR} ${BDHASH}-install 1515161748Scperciva} 1516161748Scperciva 1517161748Scperciva# Warn about any upcoming EoL 1518161748Scpercivafetch_warn_eol () { 1519161748Scperciva # What's the current time? 1520161748Scperciva NOWTIME=`date "+%s"` 1521161748Scperciva 1522161748Scperciva # When did we last warn about the EoL date? 1523161748Scperciva if [ -f lasteolwarn ]; then 1524161748Scperciva LASTWARN=`cat lasteolwarn` 1525161748Scperciva else 1526161748Scperciva LASTWARN=`expr ${NOWTIME} - 63072000` 1527161748Scperciva fi 1528161748Scperciva 1529161748Scperciva # If the EoL time is past, warn. 1530161748Scperciva if [ ${EOLTIME} -lt ${NOWTIME} ]; then 1531161748Scperciva echo 1532161748Scperciva cat <<-EOF 1533161869Scperciva WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE. 1534161748Scperciva Any security issues discovered after `date -r ${EOLTIME}` 1535161748Scperciva will not have been corrected. 1536161748Scperciva EOF 1537161748Scperciva return 1 1538161748Scperciva fi 1539161748Scperciva 1540161748Scperciva # Figure out how long it has been since we last warned about the 1541161748Scperciva # upcoming EoL, and how much longer we have left. 1542161748Scperciva SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}` 1543161748Scperciva TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}` 1544161748Scperciva 1545161748Scperciva # Don't warn if the EoL is more than 6 months away 1546161748Scperciva if [ ${TIMELEFT} -gt 15768000 ]; then 1547161748Scperciva return 0 1548161748Scperciva fi 1549161748Scperciva 1550161748Scperciva # Don't warn if the time remaining is more than 3 times the time 1551161748Scperciva # since the last warning. 1552161748Scperciva if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then 1553161748Scperciva return 0 1554161748Scperciva fi 1555161748Scperciva 1556161748Scperciva # Figure out what time units to use. 1557161748Scperciva if [ ${TIMELEFT} -lt 604800 ]; then 1558161748Scperciva UNIT="day" 1559161748Scperciva SIZE=86400 1560161748Scperciva elif [ ${TIMELEFT} -lt 2678400 ]; then 1561161748Scperciva UNIT="week" 1562161748Scperciva SIZE=604800 1563161748Scperciva else 1564161748Scperciva UNIT="month" 1565161748Scperciva SIZE=2678400 1566161748Scperciva fi 1567161748Scperciva 1568161748Scperciva # Compute the right number of units 1569161748Scperciva NUM=`expr ${TIMELEFT} / ${SIZE}` 1570161748Scperciva if [ ${NUM} != 1 ]; then 1571161748Scperciva UNIT="${UNIT}s" 1572161748Scperciva fi 1573161748Scperciva 1574161748Scperciva # Print the warning 1575161748Scperciva echo 1576161748Scperciva cat <<-EOF 1577161748Scperciva WARNING: `uname -sr` is approaching its End-of-Life date. 1578161748Scperciva It is strongly recommended that you upgrade to a newer 1579161748Scperciva release within the next ${NUM} ${UNIT}. 1580161748Scperciva EOF 1581161748Scperciva 1582161748Scperciva # Update the stored time of last warning 1583161748Scperciva echo ${NOWTIME} > lasteolwarn 1584161748Scperciva} 1585161748Scperciva 1586161748Scperciva# Do the actual work involved in "fetch" / "cron". 1587161748Scpercivafetch_run () { 1588161748Scperciva workdir_init || return 1 1589161748Scperciva 1590161748Scperciva # Prepare the mirror list. 1591161748Scperciva fetch_pick_server_init && fetch_pick_server 1592161748Scperciva 1593161748Scperciva # Try to fetch the public key until we run out of servers. 1594161748Scperciva while ! fetch_key; do 1595161748Scperciva fetch_pick_server || return 1 1596161748Scperciva done 1597161748Scperciva 1598161748Scperciva # Try to fetch the metadata index signature ("tag") until we run 1599161748Scperciva # out of available servers; and sanity check the downloaded tag. 1600161748Scperciva while ! fetch_tag; do 1601161748Scperciva fetch_pick_server || return 1 1602161748Scperciva done 1603161748Scperciva fetch_tagsanity || return 1 1604161748Scperciva 1605161748Scperciva # Fetch the latest INDEX-NEW and INDEX-OLD files. 1606161748Scperciva fetch_metadata INDEX-NEW INDEX-OLD || return 1 1607161748Scperciva 1608161748Scperciva # Generate filtered INDEX-NEW and INDEX-OLD files containing only 1609161748Scperciva # the lines which (a) belong to components we care about, and (b) 1610161748Scperciva # don't correspond to paths we're explicitly ignoring. 1611161748Scperciva fetch_filter_metadata INDEX-NEW || return 1 1612161748Scperciva fetch_filter_metadata INDEX-OLD || return 1 1613161748Scperciva 1614161748Scperciva # Translate /boot/`uname -i` into ${KERNELDIR} 1615161748Scperciva fetch_filter_kernel_names INDEX-NEW 1616161748Scperciva fetch_filter_kernel_names INDEX-OLD 1617161748Scperciva 1618161748Scperciva # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the 1619161748Scperciva # system and generate an INDEX-PRESENT file. 1620161748Scperciva fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1 1621161748Scperciva 1622161748Scperciva # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which 1623161748Scperciva # correspond to lines in INDEX-PRESENT with hashes not appearing 1624161748Scperciva # in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in 1625161748Scperciva # INDEX-PRESENT has type - and there isn't a corresponding entry in 1626161748Scperciva # INDEX-OLD with type -. 1627161748Scperciva fetch_filter_unmodified_notpresent INDEX-OLD INDEX-PRESENT INDEX-NEW 1628161748Scperciva 1629161748Scperciva # For each entry in INDEX-PRESENT of type -, remove any corresponding 1630161748Scperciva # entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries 1631161748Scperciva # of type - from INDEX-PRESENT. 1632161748Scperciva fetch_filter_allowadd INDEX-PRESENT INDEX-NEW 1633161748Scperciva 1634161748Scperciva # If ${ALLOWDELETE} != "yes", then remove any entries from 1635161748Scperciva # INDEX-PRESENT which don't correspond to entries in INDEX-NEW. 1636161748Scperciva fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW 1637161748Scperciva 1638161748Scperciva # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in 1639161748Scperciva # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD, 1640161748Scperciva # replace the corresponding line of INDEX-NEW with one having the 1641161748Scperciva # same metadata as the entry in INDEX-PRESENT. 1642161748Scperciva fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW 1643161748Scperciva 1644161748Scperciva # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical; 1645161748Scperciva # no need to update a file if it isn't changing. 1646161748Scperciva fetch_filter_uptodate INDEX-PRESENT INDEX-NEW 1647161748Scperciva 1648161748Scperciva # Prepare to fetch files: Generate a list of the files we need, 1649161748Scperciva # copy the unmodified files we have into /files/, and generate 1650161748Scperciva # a list of patches to download. 1651161748Scperciva fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW 1652161748Scperciva 1653161748Scperciva # Fetch files. 1654161748Scperciva fetch_files || return 1 1655161748Scperciva 1656161748Scperciva # Create and populate install manifest directory; and report what 1657161748Scperciva # updates are available. 1658161748Scperciva fetch_create_manifest || return 1 1659161748Scperciva 1660161748Scperciva # Warn about any upcoming EoL 1661161748Scperciva fetch_warn_eol || return 1 1662161748Scperciva} 1663161748Scperciva 1664161748Scperciva# Make sure that all the file hashes mentioned in $@ have corresponding 1665161748Scperciva# gzipped files stored in /files/. 1666161748Scpercivainstall_verify () { 1667161748Scperciva # Generate a list of hashes 1668161748Scperciva cat $@ | 1669161748Scperciva cut -f 2,7 -d '|' | 1670161748Scperciva grep -E '^f' | 1671161748Scperciva cut -f 2 -d '|' | 1672161748Scperciva sort -u > filelist 1673161748Scperciva 1674161748Scperciva # Make sure all the hashes exist 1675161748Scperciva while read HASH; do 1676161748Scperciva if ! [ -f files/${HASH}.gz ]; then 1677161748Scperciva echo -n "Update files missing -- " 1678161748Scperciva echo "this should never happen." 1679161748Scperciva echo "Re-run '$0 fetch'." 1680161748Scperciva return 1 1681161748Scperciva fi 1682161748Scperciva done < filelist 1683161748Scperciva 1684161748Scperciva # Clean up 1685161748Scperciva rm filelist 1686161748Scperciva} 1687161748Scperciva 1688161748Scperciva# Remove the system immutable flag from files 1689161748Scpercivainstall_unschg () { 1690161748Scperciva # Generate file list 1691161748Scperciva cat $@ | 1692161748Scperciva cut -f 1 -d '|' > filelist 1693161748Scperciva 1694161748Scperciva # Remove flags 1695161748Scperciva while read F; do 1696161748Scperciva if ! [ -e ${F} ]; then 1697161748Scperciva continue 1698161748Scperciva fi 1699161748Scperciva 1700161748Scperciva chflags noschg ${F} || return 1 1701161748Scperciva done < filelist 1702161748Scperciva 1703161748Scperciva # Clean up 1704161748Scperciva rm filelist 1705161748Scperciva} 1706161748Scperciva 1707161748Scperciva# Install new files 1708161748Scpercivainstall_from_index () { 1709161748Scperciva # First pass: Do everything apart from setting file flags. We 1710161748Scperciva # can't set flags yet, because schg inhibits hard linking. 1711161748Scperciva sort -k 1,1 -t '|' $1 | 1712161748Scperciva tr '|' ' ' | 1713161748Scperciva while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do 1714161748Scperciva case ${TYPE} in 1715161748Scperciva d) 1716161748Scperciva # Create a directory 1717161748Scperciva install -d -o ${OWNER} -g ${GROUP} \ 1718161748Scperciva -m ${PERM} ${BASEDIR}/${FPATH} 1719161748Scperciva ;; 1720161748Scperciva f) 1721161748Scperciva if [ -z "${LINK}" ]; then 1722161748Scperciva # Create a file, without setting flags. 1723161748Scperciva gunzip < files/${HASH}.gz > ${HASH} 1724161748Scperciva install -S -o ${OWNER} -g ${GROUP} \ 1725161748Scperciva -m ${PERM} ${HASH} ${BASEDIR}/${FPATH} 1726161748Scperciva rm ${HASH} 1727161748Scperciva else 1728161748Scperciva # Create a hard link. 1729161748Scperciva ln -f ${LINK} ${BASEDIR}/${FPATH} 1730161748Scperciva fi 1731161748Scperciva ;; 1732161748Scperciva L) 1733161748Scperciva # Create a symlink 1734161748Scperciva ln -sfh ${HASH} ${BASEDIR}/${FPATH} 1735161748Scperciva ;; 1736161748Scperciva esac 1737161748Scperciva done 1738161748Scperciva 1739161748Scperciva # Perform a second pass, adding file flags. 1740161748Scperciva tr '|' ' ' < $1 | 1741161748Scperciva while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do 1742161748Scperciva if [ ${TYPE} = "f" ] && 1743161748Scperciva ! [ ${FLAGS} = "0" ]; then 1744161748Scperciva chflags ${FLAGS} ${BASEDIR}/${FPATH} 1745161748Scperciva fi 1746161748Scperciva done 1747161748Scperciva} 1748161748Scperciva 1749161748Scperciva# Remove files which we want to delete 1750161748Scpercivainstall_delete () { 1751161748Scperciva # Generate list of new files 1752161748Scperciva cut -f 1 -d '|' < $2 | 1753161748Scperciva sort > newfiles 1754161748Scperciva 1755161748Scperciva # Generate subindex of old files we want to nuke 1756161748Scperciva sort -k 1,1 -t '|' $1 | 1757161748Scperciva join -t '|' -v 1 - newfiles | 1758164600Scperciva sort -r -k 1,1 -t '|' | 1759161748Scperciva cut -f 1,2 -d '|' | 1760161748Scperciva tr '|' ' ' > killfiles 1761161748Scperciva 1762161748Scperciva # Remove the offending bits 1763161748Scperciva while read FPATH TYPE; do 1764161748Scperciva case ${TYPE} in 1765161748Scperciva d) 1766161748Scperciva rmdir ${BASEDIR}/${FPATH} 1767161748Scperciva ;; 1768161748Scperciva f) 1769161748Scperciva rm ${BASEDIR}/${FPATH} 1770161748Scperciva ;; 1771161748Scperciva L) 1772161748Scperciva rm ${BASEDIR}/${FPATH} 1773161748Scperciva ;; 1774161748Scperciva esac 1775161748Scperciva done < killfiles 1776161748Scperciva 1777161748Scperciva # Clean up 1778161748Scperciva rm newfiles killfiles 1779161748Scperciva} 1780161748Scperciva 1781161748Scperciva# Update linker.hints if anything in /boot/ was touched 1782161748Scpercivainstall_kldxref () { 1783161748Scperciva if cat $@ | 1784161748Scperciva grep -qE '^/boot/'; then 1785161748Scperciva kldxref -R /boot/ 1786161748Scperciva fi 1787161748Scperciva} 1788161748Scperciva 1789161748Scperciva# Rearrange bits to allow the installed updates to be rolled back 1790161748Scpercivainstall_setup_rollback () { 1791161748Scperciva if [ -L ${BDHASH}-rollback ]; then 1792161748Scperciva mv ${BDHASH}-rollback ${BDHASH}-install/rollback 1793161748Scperciva fi 1794161748Scperciva 1795161748Scperciva mv ${BDHASH}-install ${BDHASH}-rollback 1796161748Scperciva} 1797161748Scperciva 1798161748Scperciva# Actually install updates 1799161748Scpercivainstall_run () { 1800161748Scperciva echo -n "Installing updates..." 1801161748Scperciva 1802161748Scperciva # Make sure we have all the files we should have 1803161748Scperciva install_verify ${BDHASH}-install/INDEX-OLD \ 1804161748Scperciva ${BDHASH}-install/INDEX-NEW || return 1 1805161748Scperciva 1806161748Scperciva # Remove system immutable flag from files 1807161748Scperciva install_unschg ${BDHASH}-install/INDEX-OLD \ 1808161748Scperciva ${BDHASH}-install/INDEX-NEW || return 1 1809161748Scperciva 1810161748Scperciva # Install new files 1811161748Scperciva install_from_index \ 1812161748Scperciva ${BDHASH}-install/INDEX-NEW || return 1 1813161748Scperciva 1814161748Scperciva # Remove files which we want to delete 1815161748Scperciva install_delete ${BDHASH}-install/INDEX-OLD \ 1816161748Scperciva ${BDHASH}-install/INDEX-NEW || return 1 1817161748Scperciva 1818161748Scperciva # Update linker.hints if anything in /boot/ was touched 1819161748Scperciva install_kldxref ${BDHASH}-install/INDEX-OLD \ 1820161748Scperciva ${BDHASH}-install/INDEX-NEW 1821161748Scperciva 1822161748Scperciva # Rearrange bits to allow the installed updates to be rolled back 1823161748Scperciva install_setup_rollback 1824161748Scperciva 1825161748Scperciva echo " done." 1826161748Scperciva} 1827161748Scperciva 1828161748Scperciva# Rearrange bits to allow the previous set of updates to be rolled back next. 1829161748Scpercivarollback_setup_rollback () { 1830161748Scperciva if [ -L ${BDHASH}-rollback/rollback ]; then 1831161748Scperciva mv ${BDHASH}-rollback/rollback rollback-tmp 1832161748Scperciva rm -r ${BDHASH}-rollback/ 1833161748Scperciva rm ${BDHASH}-rollback 1834161748Scperciva mv rollback-tmp ${BDHASH}-rollback 1835161748Scperciva else 1836161748Scperciva rm -r ${BDHASH}-rollback/ 1837161748Scperciva rm ${BDHASH}-rollback 1838161748Scperciva fi 1839161748Scperciva} 1840161748Scperciva 1841161748Scperciva# Actually rollback updates 1842161748Scpercivarollback_run () { 1843161748Scperciva echo -n "Uninstalling updates..." 1844161748Scperciva 1845161748Scperciva # If there are updates waiting to be installed, remove them; we 1846161748Scperciva # want the user to re-run 'fetch' after rolling back updates. 1847161748Scperciva if [ -L ${BDHASH}-install ]; then 1848161748Scperciva rm -r ${BDHASH}-install/ 1849161748Scperciva rm ${BDHASH}-install 1850161748Scperciva fi 1851161748Scperciva 1852161748Scperciva # Make sure we have all the files we should have 1853161748Scperciva install_verify ${BDHASH}-rollback/INDEX-NEW \ 1854161748Scperciva ${BDHASH}-rollback/INDEX-OLD || return 1 1855161748Scperciva 1856161748Scperciva # Remove system immutable flag from files 1857161748Scperciva install_unschg ${BDHASH}-rollback/INDEX-NEW \ 1858161748Scperciva ${BDHASH}-rollback/INDEX-OLD || return 1 1859161748Scperciva 1860161748Scperciva # Install new files 1861161748Scperciva install_from_index \ 1862161748Scperciva ${BDHASH}-rollback/INDEX-OLD || return 1 1863161748Scperciva 1864161748Scperciva # Remove files which we want to delete 1865161748Scperciva install_delete ${BDHASH}-rollback/INDEX-NEW \ 1866161748Scperciva ${BDHASH}-rollback/INDEX-OLD || return 1 1867161748Scperciva 1868161748Scperciva # Update linker.hints if anything in /boot/ was touched 1869161748Scperciva install_kldxref ${BDHASH}-rollback/INDEX-NEW \ 1870161748Scperciva ${BDHASH}-rollback/INDEX-OLD 1871161748Scperciva 1872161748Scperciva # Remove the rollback directory and the symlink pointing to it; and 1873161748Scperciva # rearrange bits to allow the previous set of updates to be rolled 1874161748Scperciva # back next. 1875161748Scperciva rollback_setup_rollback 1876161748Scperciva 1877161748Scperciva echo " done." 1878161748Scperciva} 1879161748Scperciva 1880161748Scperciva#### Main functions -- call parameter-handling and core functions 1881161748Scperciva 1882161748Scperciva# Using the command line, configuration file, and defaults, 1883161748Scperciva# set all the parameters which are needed later. 1884161748Scpercivaget_params () { 1885161748Scperciva init_params 1886161748Scperciva parse_cmdline $@ 1887161748Scperciva parse_conffile 1888161748Scperciva default_params 1889161748Scperciva} 1890161748Scperciva 1891161748Scperciva# Fetch command. Make sure that we're being called 1892161748Scperciva# interactively, then run fetch_check_params and fetch_run 1893161748Scpercivacmd_fetch () { 1894161748Scperciva if [ ! -t 0 ]; then 1895161748Scperciva echo -n "`basename $0` fetch should not " 1896161748Scperciva echo "be run non-interactively." 1897161748Scperciva echo "Run `basename $0` cron instead." 1898161748Scperciva exit 1 1899161748Scperciva fi 1900161748Scperciva fetch_check_params 1901161748Scperciva fetch_run || exit 1 1902161748Scperciva} 1903161748Scperciva 1904161748Scperciva# Cron command. Make sure the parameters are sensible; wait 1905161748Scperciva# rand(3600) seconds; then fetch updates. While fetching updates, 1906161748Scperciva# send output to a temporary file; only print that file if the 1907161748Scperciva# fetching failed. 1908161748Scpercivacmd_cron () { 1909161748Scperciva fetch_check_params 1910161748Scperciva sleep `jot -r 1 0 3600` 1911161748Scperciva 1912161748Scperciva TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1 1913161748Scperciva if ! fetch_run >> ${TMPFILE} || 1914161748Scperciva ! grep -q "No updates needed" ${TMPFILE} || 1915161748Scperciva [ ${VERBOSELEVEL} = "debug" ]; then 1916161748Scperciva mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE} 1917161748Scperciva fi 1918161748Scperciva 1919161748Scperciva rm ${TMPFILE} 1920161748Scperciva} 1921161748Scperciva 1922161748Scperciva# Install downloaded updates. 1923161748Scpercivacmd_install () { 1924161748Scperciva install_check_params 1925161748Scperciva install_run || exit 1 1926161748Scperciva} 1927161748Scperciva 1928161748Scperciva# Rollback most recently installed updates. 1929161748Scpercivacmd_rollback () { 1930161748Scperciva rollback_check_params 1931161748Scperciva rollback_run || exit 1 1932161748Scperciva} 1933161748Scperciva 1934161748Scperciva#### Entry point 1935161748Scperciva 1936161748Scperciva# Make sure we find utilities from the base system 1937161748Scpercivaexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH} 1938161748Scperciva 1939163564Scperciva# Set LC_ALL in order to avoid problems with character ranges like [A-Z]. 1940163564Scpercivaexport LC_ALL=C 1941163564Scperciva 1942161748Scpercivaget_params $@ 1943161748Scpercivafor COMMAND in ${COMMANDS}; do 1944161748Scperciva cmd_${COMMAND} 1945161748Scpercivadone 1946