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