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