1#!/bin/sh 2 3#- 4# SPDX-License-Identifier: BSD-2-Clause 5# 6# Copyright 2004-2007 Colin Percival 7# All rights reserved 8# 9# Redistribution and use in source and binary forms, with or without 10# modification, are permitted providing that the following conditions 11# are met: 12# 1. Redistributions of source code must retain the above copyright 13# notice, this list of conditions and the following disclaimer. 14# 2. Redistributions in binary form must reproduce the above copyright 15# notice, this list of conditions and the following disclaimer in the 16# documentation and/or other materials provided with the distribution. 17# 18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 22# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 27# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28# POSSIBILITY OF SUCH DAMAGE. 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 ... 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 -F -- Force a fetch operation to proceed in the 47 case of an unfinished upgrade 48 -j jail -- Operate on the given jail specified by jid or name 49 -k KEY -- Trust an RSA key with SHA256 hash of KEY 50 -r release -- Target for upgrade (e.g., 13.2-RELEASE) 51 -s server -- Server from which to fetch updates 52 (default: update.FreeBSD.org) 53 -t address -- Mail output of cron command, if any, to address 54 (default: root) 55 --not-running-from-cron 56 -- Run without a tty, for use by automated tools 57 --currently-running release 58 -- Update as if currently running this release 59Commands: 60 fetch -- Fetch updates from server 61 cron -- Sleep rand(3600) seconds, fetch updates, and send an 62 email if updates were found 63 upgrade -- Fetch upgrades to FreeBSD version specified via -r option 64 updatesready -- Check if there are fetched updates ready to install 65 install -- Install downloaded updates or upgrades 66 rollback -- Uninstall most recently installed updates 67 IDS -- Compare the system against an index of "known good" files 68 showconfig -- Show configuration 69EOF 70 exit 0 71} 72 73#### Configuration processing functions 74 75#- 76# Configuration options are set in the following order of priority: 77# 1. Command line options 78# 2. Configuration file options 79# 3. Default options 80# In addition, certain options (e.g., IgnorePaths) can be specified multiple 81# times and (as long as these are all in the same place, e.g., inside the 82# configuration file) they will accumulate. Finally, because the path to the 83# configuration file can be specified at the command line, the entire command 84# line must be processed before we start reading the configuration file. 85# 86# Sound like a mess? It is. Here's how we handle this: 87# 1. Initialize CONFFILE and all the options to "". 88# 2. Process the command line. Throw an error if a non-accumulating option 89# is specified twice. 90# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf . 91# 4. For all the configuration options X, set X_saved to X. 92# 5. Initialize all the options to "". 93# 6. Read CONFFILE line by line, parsing options. 94# 7. For each configuration option X, set X to X_saved iff X_saved is not "". 95# 8. Repeat steps 4-7, except setting options to their default values at (6). 96 97CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE 98 KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED 99 BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES 100 IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES" 101 102# Set all the configuration options to "". 103nullconfig () { 104 for X in ${CONFIGOPTIONS}; do 105 eval ${X}="" 106 done 107} 108 109# For each configuration option X, set X_saved to X. 110saveconfig () { 111 for X in ${CONFIGOPTIONS}; do 112 eval ${X}_saved=\$${X} 113 done 114} 115 116# For each configuration option X, set X to X_saved if X_saved is not "". 117mergeconfig () { 118 for X in ${CONFIGOPTIONS}; do 119 eval _=\$${X}_saved 120 if ! [ -z "${_}" ]; then 121 eval ${X}=\$${X}_saved 122 fi 123 done 124} 125 126# Set the trusted keyprint. 127config_KeyPrint () { 128 if [ -z ${KEYPRINT} ]; then 129 KEYPRINT=$1 130 else 131 return 1 132 fi 133} 134 135# Set the working directory. 136config_WorkDir () { 137 if [ -z ${WORKDIR} ]; then 138 WORKDIR=$1 139 else 140 return 1 141 fi 142} 143 144# Set the name of the server (pool) from which to fetch updates 145config_ServerName () { 146 if [ -z ${SERVERNAME} ]; then 147 SERVERNAME=$1 148 else 149 return 1 150 fi 151} 152 153# Set the address to which 'cron' output will be mailed. 154config_MailTo () { 155 if [ -z ${MAILTO} ]; then 156 MAILTO=$1 157 else 158 return 1 159 fi 160} 161 162# Set whether FreeBSD Update is allowed to add files (or directories, or 163# symlinks) which did not previously exist. 164config_AllowAdd () { 165 if [ -z ${ALLOWADD} ]; then 166 case $1 in 167 [Yy][Ee][Ss]) 168 ALLOWADD=yes 169 ;; 170 [Nn][Oo]) 171 ALLOWADD=no 172 ;; 173 *) 174 return 1 175 ;; 176 esac 177 else 178 return 1 179 fi 180} 181 182# Set whether FreeBSD Update is allowed to remove files/directories/symlinks. 183config_AllowDelete () { 184 if [ -z ${ALLOWDELETE} ]; then 185 case $1 in 186 [Yy][Ee][Ss]) 187 ALLOWDELETE=yes 188 ;; 189 [Nn][Oo]) 190 ALLOWDELETE=no 191 ;; 192 *) 193 return 1 194 ;; 195 esac 196 else 197 return 1 198 fi 199} 200 201# Set whether FreeBSD Update should keep existing inode ownership, 202# permissions, and flags, in the event that they have been modified locally 203# after the release. 204config_KeepModifiedMetadata () { 205 if [ -z ${KEEPMODIFIEDMETADATA} ]; then 206 case $1 in 207 [Yy][Ee][Ss]) 208 KEEPMODIFIEDMETADATA=yes 209 ;; 210 [Nn][Oo]) 211 KEEPMODIFIEDMETADATA=no 212 ;; 213 *) 214 return 1 215 ;; 216 esac 217 else 218 return 1 219 fi 220} 221 222# Add to the list of components which should be kept updated. 223config_Components () { 224 for C in $@; do 225 COMPONENTS="${COMPONENTS} ${C}" 226 done 227} 228 229# Remove src component from list if it isn't installed 230finalize_components_config () { 231 COMPONENTS="" 232 for C in $@; do 233 if [ "$C" = "src" ]; then 234 if [ -e "${BASEDIR}/usr/src/COPYRIGHT" ]; then 235 COMPONENTS="${COMPONENTS} ${C}" 236 else 237 echo "src component not installed, skipped" 238 fi 239 else 240 COMPONENTS="${COMPONENTS} ${C}" 241 fi 242 done 243} 244 245# Add to the list of paths under which updates will be ignored. 246config_IgnorePaths () { 247 for C in $@; do 248 IGNOREPATHS="${IGNOREPATHS} ${C}" 249 done 250} 251 252# Add to the list of paths which IDS should ignore. 253config_IDSIgnorePaths () { 254 for C in $@; do 255 IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}" 256 done 257} 258 259# Add to the list of paths within which updates will be performed only if the 260# file on disk has not been modified locally. 261config_UpdateIfUnmodified () { 262 for C in $@; do 263 UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}" 264 done 265} 266 267# Add to the list of paths within which updates to text files will be merged 268# instead of overwritten. 269config_MergeChanges () { 270 for C in $@; do 271 MERGECHANGES="${MERGECHANGES} ${C}" 272 done 273} 274 275# Work on a FreeBSD installation mounted under $1 276config_BaseDir () { 277 if [ -z ${BASEDIR} ]; then 278 BASEDIR=$1 279 else 280 return 1 281 fi 282} 283 284# When fetching upgrades, should we assume the user wants exactly the 285# components listed in COMPONENTS, rather than trying to guess based on 286# what's currently installed? 287config_StrictComponents () { 288 if [ -z ${STRICTCOMPONENTS} ]; then 289 case $1 in 290 [Yy][Ee][Ss]) 291 STRICTCOMPONENTS=yes 292 ;; 293 [Nn][Oo]) 294 STRICTCOMPONENTS=no 295 ;; 296 *) 297 return 1 298 ;; 299 esac 300 else 301 return 1 302 fi 303} 304 305# Upgrade to FreeBSD $1 306config_TargetRelease () { 307 if [ -z ${TARGETRELEASE} ]; then 308 TARGETRELEASE=$1 309 else 310 return 1 311 fi 312 if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then 313 TARGETRELEASE="${TARGETRELEASE}-RELEASE" 314 fi 315} 316 317# Pretend current release is FreeBSD $1 318config_SourceRelease () { 319 UNAME_r=$1 320 if echo ${UNAME_r} | grep -qE '^[0-9.]+$'; then 321 UNAME_r="${UNAME_r}-RELEASE" 322 fi 323 export UNAME_r 324} 325 326# Get the Jail's path and the version of its installed userland 327config_TargetJail () { 328 JAIL=$1 329 UNAME_r=$(freebsd-version -j ${JAIL}) 330 BASEDIR=$(jls -j ${JAIL} -h path | awk 'NR == 2 {print}') 331 if [ -z ${BASEDIR} ] || [ -z ${UNAME_r} ]; then 332 echo "The specified jail either doesn't exist or" \ 333 "does not have freebsd-version." 334 exit 1 335 fi 336 export UNAME_r 337} 338 339# Define what happens to output of utilities 340config_VerboseLevel () { 341 if [ -z ${VERBOSELEVEL} ]; then 342 case $1 in 343 [Dd][Ee][Bb][Uu][Gg]) 344 VERBOSELEVEL=debug 345 ;; 346 [Nn][Oo][Ss][Tt][Aa][Tt][Ss]) 347 VERBOSELEVEL=nostats 348 ;; 349 [Ss][Tt][Aa][Tt][Ss]) 350 VERBOSELEVEL=stats 351 ;; 352 *) 353 return 1 354 ;; 355 esac 356 else 357 return 1 358 fi 359} 360 361config_BackupKernel () { 362 if [ -z ${BACKUPKERNEL} ]; then 363 case $1 in 364 [Yy][Ee][Ss]) 365 BACKUPKERNEL=yes 366 ;; 367 [Nn][Oo]) 368 BACKUPKERNEL=no 369 ;; 370 *) 371 return 1 372 ;; 373 esac 374 else 375 return 1 376 fi 377} 378 379config_BackupKernelDir () { 380 if [ -z ${BACKUPKERNELDIR} ]; then 381 if [ -z "$1" ]; then 382 echo "BackupKernelDir set to empty dir" 383 return 1 384 fi 385 386 # We check for some paths which would be extremely odd 387 # to use, but which could cause a lot of problems if 388 # used. 389 case $1 in 390 /|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var) 391 echo "BackupKernelDir set to invalid path $1" 392 return 1 393 ;; 394 /*) 395 BACKUPKERNELDIR=$1 396 ;; 397 *) 398 echo "BackupKernelDir ($1) is not an absolute path" 399 return 1 400 ;; 401 esac 402 else 403 return 1 404 fi 405} 406 407config_BackupKernelSymbolFiles () { 408 if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then 409 case $1 in 410 [Yy][Ee][Ss]) 411 BACKUPKERNELSYMBOLFILES=yes 412 ;; 413 [Nn][Oo]) 414 BACKUPKERNELSYMBOLFILES=no 415 ;; 416 *) 417 return 1 418 ;; 419 esac 420 else 421 return 1 422 fi 423} 424 425config_CreateBootEnv () { 426 if [ -z ${BOOTENV} ]; then 427 case $1 in 428 [Yy][Ee][Ss]) 429 BOOTENV=yes 430 ;; 431 [Nn][Oo]) 432 BOOTENV=no 433 ;; 434 *) 435 return 1 436 ;; 437 esac 438 else 439 return 1 440 fi 441} 442# Handle one line of configuration 443configline () { 444 if [ $# -eq 0 ]; then 445 return 446 fi 447 448 OPT=$1 449 shift 450 config_${OPT} $@ 451} 452 453#### Parameter handling functions. 454 455# Initialize parameters to null, just in case they're 456# set in the environment. 457init_params () { 458 # Configration settings 459 nullconfig 460 461 # No configuration file set yet 462 CONFFILE="" 463 464 # No commands specified yet 465 COMMANDS="" 466 467 # Force fetch to proceed 468 FORCEFETCH=0 469 470 # Run without a TTY 471 NOTTYOK=0 472 473 # Fetched first in a chain of commands 474 ISFETCHED=0 475} 476 477# Parse the command line 478parse_cmdline () { 479 while [ $# -gt 0 ]; do 480 case "$1" in 481 # Location of configuration file 482 -f) 483 if [ $# -eq 1 ]; then usage; fi 484 if [ ! -z "${CONFFILE}" ]; then usage; fi 485 shift; CONFFILE="$1" 486 ;; 487 -F) 488 FORCEFETCH=1 489 ;; 490 --not-running-from-cron) 491 NOTTYOK=1 492 ;; 493 --currently-running) 494 shift 495 config_SourceRelease $1 || usage 496 ;; 497 498 # Configuration file equivalents 499 -b) 500 if [ $# -eq 1 ]; then usage; fi; shift 501 config_BaseDir $1 || usage 502 ;; 503 -d) 504 if [ $# -eq 1 ]; then usage; fi; shift 505 config_WorkDir $1 || usage 506 ;; 507 -j) 508 if [ $# -eq 1 ]; then usage; fi; shift 509 config_TargetJail $1 || usage 510 ;; 511 -k) 512 if [ $# -eq 1 ]; then usage; fi; shift 513 config_KeyPrint $1 || usage 514 ;; 515 -s) 516 if [ $# -eq 1 ]; then usage; fi; shift 517 config_ServerName $1 || usage 518 ;; 519 -r) 520 if [ $# -eq 1 ]; then usage; fi; shift 521 config_TargetRelease $1 || usage 522 ;; 523 -t) 524 if [ $# -eq 1 ]; then usage; fi; shift 525 config_MailTo $1 || usage 526 ;; 527 -v) 528 if [ $# -eq 1 ]; then usage; fi; shift 529 config_VerboseLevel $1 || usage 530 ;; 531 532 # Aliases for "-v debug" and "-v nostats" 533 --debug) 534 config_VerboseLevel debug || usage 535 ;; 536 --no-stats) 537 config_VerboseLevel nostats || usage 538 ;; 539 540 # Commands 541 cron | fetch | upgrade | updatesready | install | rollback |\ 542 IDS | showconfig) 543 COMMANDS="${COMMANDS} $1" 544 ;; 545 546 # Anything else is an error 547 *) 548 usage 549 ;; 550 esac 551 shift 552 done 553 554 # Make sure we have at least one command 555 if [ -z "${COMMANDS}" ]; then 556 usage 557 fi 558} 559 560# Parse the configuration file 561parse_conffile () { 562 # If a configuration file was specified on the command line, check 563 # that it exists and is readable. 564 if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then 565 echo -n "File does not exist " 566 echo -n "or is not readable: " 567 echo ${CONFFILE} 568 exit 1 569 fi 570 571 # If a configuration file was not specified on the command line, 572 # use the default configuration file path. If that default does 573 # not exist, give up looking for any configuration. 574 if [ -z "${CONFFILE}" ]; then 575 CONFFILE="/etc/freebsd-update.conf" 576 if [ ! -r "${CONFFILE}" ]; then 577 return 578 fi 579 fi 580 581 # Save the configuration options specified on the command line, and 582 # clear all the options in preparation for reading the config file. 583 saveconfig 584 nullconfig 585 586 # Read the configuration file. Anything after the first '#' is 587 # ignored, and any blank lines are ignored. 588 L=0 589 while read LINE; do 590 L=$(($L + 1)) 591 LINEX=`echo "${LINE}" | cut -f 1 -d '#'` 592 if ! configline ${LINEX}; then 593 echo "Error processing configuration file, line $L:" 594 echo "==> ${LINE}" 595 exit 1 596 fi 597 done < ${CONFFILE} 598 599 # Merge the settings read from the configuration file with those 600 # provided at the command line. 601 mergeconfig 602} 603 604# Provide some default parameters 605default_params () { 606 # Save any parameters already configured, and clear the slate 607 saveconfig 608 nullconfig 609 610 # Default configurations 611 config_WorkDir /var/db/freebsd-update 612 config_MailTo root 613 config_AllowAdd yes 614 config_AllowDelete yes 615 config_KeepModifiedMetadata yes 616 config_BaseDir / 617 config_VerboseLevel stats 618 config_StrictComponents no 619 config_BackupKernel yes 620 config_BackupKernelDir /boot/kernel.old 621 config_BackupKernelSymbolFiles no 622 config_CreateBootEnv yes 623 624 # Merge these defaults into the earlier-configured settings 625 mergeconfig 626} 627 628# Set utility output filtering options, based on ${VERBOSELEVEL} 629fetch_setup_verboselevel () { 630 case ${VERBOSELEVEL} in 631 debug) 632 QUIETREDIR="/dev/stderr" 633 QUIETFLAG=" " 634 STATSREDIR="/dev/stderr" 635 DDSTATS=".." 636 XARGST="-t" 637 NDEBUG=" " 638 ;; 639 nostats) 640 QUIETREDIR="" 641 QUIETFLAG="" 642 STATSREDIR="/dev/null" 643 DDSTATS=".." 644 XARGST="" 645 NDEBUG="" 646 ;; 647 stats) 648 QUIETREDIR="/dev/null" 649 QUIETFLAG="-q" 650 STATSREDIR="/dev/stdout" 651 DDSTATS="" 652 XARGST="" 653 NDEBUG="-n" 654 ;; 655 esac 656} 657 658# Perform sanity checks and set some final parameters 659# in preparation for fetching files. Figure out which 660# set of updates should be downloaded: If the user is 661# running *-p[0-9]+, strip off the last part; if the 662# user is running -SECURITY, call it -RELEASE. Chdir 663# into the working directory. 664fetchupgrade_check_params () { 665 export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)" 666 667 _SERVERNAME_z=\ 668"SERVERNAME must be given via command line or configuration file." 669 _KEYPRINT_z="Key must be given via -k option or configuration file." 670 _KEYPRINT_bad="Invalid key fingerprint: " 671 _WORKDIR_bad="Directory does not exist or is not writable: " 672 _WORKDIR_bad2="Directory is not on a persistent filesystem: " 673 674 if [ -z "${SERVERNAME}" ]; then 675 echo -n "`basename $0`: " 676 echo "${_SERVERNAME_z}" 677 exit 1 678 fi 679 if [ -z "${KEYPRINT}" ]; then 680 echo -n "`basename $0`: " 681 echo "${_KEYPRINT_z}" 682 exit 1 683 fi 684 if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then 685 echo -n "`basename $0`: " 686 echo -n "${_KEYPRINT_bad}" 687 echo ${KEYPRINT} 688 exit 1 689 fi 690 if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 691 echo -n "`basename $0`: " 692 echo -n "${_WORKDIR_bad}" 693 echo ${WORKDIR} 694 exit 1 695 fi 696 case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*) 697 echo -n "`basename $0`: " 698 echo -n "${_WORKDIR_bad2}" 699 echo ${WORKDIR} 700 exit 1 701 ;; 702 esac 703 chmod 700 ${WORKDIR} 704 cd ${WORKDIR} || exit 1 705 706 # Generate release number. The s/SECURITY/RELEASE/ bit exists 707 # to provide an upgrade path for FreeBSD Update 1.x users, since 708 # the kernels provided by FreeBSD Update 1.x are always labelled 709 # as X.Y-SECURITY. 710 RELNUM=`uname -r | 711 sed -E 's,-p[0-9]+,,' | 712 sed -E 's,-SECURITY,-RELEASE,'` 713 ARCH=`uname -m` 714 FETCHDIR=${RELNUM}/${ARCH} 715 PATCHDIR=${RELNUM}/${ARCH}/bp 716 717 # Disallow upgrade from a version that is not a release 718 case ${RELNUM} in 719 *-RELEASE | *-ALPHA* | *-BETA* | *-RC*) 720 ;; 721 *) 722 echo -n "`basename $0`: " 723 cat <<- EOF 724 Cannot upgrade from a version that is not a release 725 (including alpha, beta and release candidates) 726 using `basename $0`. Instead, FreeBSD can be directly 727 upgraded by source or upgraded to a RELEASE/RELENG version 728 prior to running `basename $0`. 729 Currently running: ${RELNUM} 730 EOF 731 exit 1 732 ;; 733 esac 734 735 # Figure out what directory contains the running kernel 736 BOOTFILE=`sysctl -n kern.bootfile` 737 KERNELDIR=${BOOTFILE%/kernel} 738 if ! [ -d ${KERNELDIR} ]; then 739 echo "Cannot identify running kernel" 740 exit 1 741 fi 742 743 # Figure out what kernel configuration is running. We start with 744 # the output of `uname -i`, and then make the following adjustments: 745 # 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config 746 # file says "ident SMP-GENERIC", I don't know... 747 # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64" 748 # _and_ `sysctl kern.version` contains a line which ends "/SMP", then 749 # we're running an SMP kernel. This mis-identification is a bug 750 # which was fixed in 6.2-STABLE. 751 KERNCONF=`uname -i` 752 if [ ${KERNCONF} = "SMP-GENERIC" ]; then 753 KERNCONF=SMP 754 fi 755 if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then 756 if sysctl kern.version | grep -qE '/SMP$'; then 757 KERNCONF=SMP 758 fi 759 fi 760 761 # Define some paths 762 BSPATCH=/usr/bin/bspatch 763 SHA256=/sbin/sha256 764 PHTTPGET=/usr/libexec/phttpget 765 766 # Set up variables relating to VERBOSELEVEL 767 fetch_setup_verboselevel 768 769 # Construct a unique name from ${BASEDIR} 770 BDHASH=`echo ${BASEDIR} | sha256 -q` 771} 772 773# Perform sanity checks etc. before fetching updates. 774fetch_check_params () { 775 fetchupgrade_check_params 776 777 if ! [ -z "${TARGETRELEASE}" ]; then 778 echo -n "`basename $0`: " 779 echo -n "'-r' option is meaningless with 'fetch' command. " 780 echo "(Did you mean 'upgrade' instead?)" 781 exit 1 782 fi 783 784 # Check that we have updates ready to install 785 if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then 786 echo "You have a partially completed upgrade pending" 787 echo "Run '`basename $0` [options] install' first." 788 echo "Run '`basename $0` [options] fetch -F' to proceed anyway." 789 exit 1 790 fi 791} 792 793# Perform sanity checks etc. before fetching upgrades. 794upgrade_check_params () { 795 fetchupgrade_check_params 796 797 # Unless set otherwise, we're upgrading to the same kernel config. 798 NKERNCONF=${KERNCONF} 799 800 # We need TARGETRELEASE set 801 _TARGETRELEASE_z="Release target must be specified via '-r' option." 802 if [ -z "${TARGETRELEASE}" ]; then 803 echo -n "`basename $0`: " 804 echo "${_TARGETRELEASE_z}" 805 exit 1 806 fi 807 808 # The target release should be != the current release. 809 if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then 810 echo -n "`basename $0`: " 811 echo "Cannot upgrade from ${RELNUM} to itself" 812 exit 1 813 fi 814 815 # Turning off AllowAdd or AllowDelete is a bad idea for upgrades. 816 if [ "${ALLOWADD}" = "no" ]; then 817 echo -n "`basename $0`: " 818 echo -n "WARNING: \"AllowAdd no\" is a bad idea " 819 echo "when upgrading between releases." 820 echo 821 fi 822 if [ "${ALLOWDELETE}" = "no" ]; then 823 echo -n "`basename $0`: " 824 echo -n "WARNING: \"AllowDelete no\" is a bad idea " 825 echo "when upgrading between releases." 826 echo 827 fi 828 829 # Set EDITOR to /usr/bin/vi if it isn't already set 830 : ${EDITOR:='/usr/bin/vi'} 831} 832 833# Perform sanity checks and set some final parameters in 834# preparation for installing updates. 835install_check_params () { 836 # Check that we are root. All sorts of things won't work otherwise. 837 if [ `id -u` != 0 ]; then 838 echo "You must be root to run this." 839 exit 1 840 fi 841 842 # Check that securelevel <= 0. Otherwise we can't update schg files. 843 if [ `sysctl -n kern.securelevel` -gt 0 ]; then 844 echo "Updates cannot be installed when the system securelevel" 845 echo "is greater than zero." 846 exit 1 847 fi 848 849 # Check that we have a working directory 850 _WORKDIR_bad="Directory does not exist or is not writable: " 851 if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 852 echo -n "`basename $0`: " 853 echo -n "${_WORKDIR_bad}" 854 echo ${WORKDIR} 855 exit 1 856 fi 857 cd ${WORKDIR} || exit 1 858 859 # Construct a unique name from ${BASEDIR} 860 BDHASH=`echo ${BASEDIR} | sha256 -q` 861 862 # Check that we have updates ready to install 863 if ! [ -L ${BDHASH}-install ]; then 864 echo "No updates are available to install." 865 if [ $ISFETCHED -eq 0 ]; then 866 echo "Run '`basename $0` [options] fetch' first." 867 exit 2 868 fi 869 exit 0 870 fi 871 if ! [ -f ${BDHASH}-install/INDEX-OLD ] || 872 ! [ -f ${BDHASH}-install/INDEX-NEW ]; then 873 echo "Update manifest is corrupt -- this should never happen." 874 echo "Re-run '`basename $0` [options] fetch'." 875 exit 1 876 fi 877 878 # Figure out what directory contains the running kernel 879 BOOTFILE=`sysctl -n kern.bootfile` 880 KERNELDIR=${BOOTFILE%/kernel} 881 if ! [ -d ${KERNELDIR} ]; then 882 echo "Cannot identify running kernel" 883 exit 1 884 fi 885} 886 887# Creates a new boot environment 888install_create_be () { 889 # Figure out if we're running in a jail and return if we are 890 if [ `sysctl -n security.jail.jailed` = 1 ]; then 891 return 1 892 fi 893 # Operating on roots that aren't located at / will, more often than not, 894 # not touch the boot environment. 895 if [ "$BASEDIR" != "/" ]; then 896 return 1 897 fi 898 # Create a boot environment if enabled 899 if [ ${BOOTENV} = yes ]; then 900 bectl check 2>/dev/null 901 case $? in 902 0) 903 # Boot environment are supported 904 CREATEBE=yes 905 ;; 906 255) 907 # Boot environments are not supported 908 CREATEBE=no 909 ;; 910 *) 911 # If bectl returns an unexpected exit code, don't create a BE 912 CREATEBE=no 913 ;; 914 esac 915 if [ ${CREATEBE} = yes ]; then 916 echo -n "Creating snapshot of existing boot environment... " 917 VERSION=`freebsd-version -ku | sort -V | tail -n 1` 918 TIMESTAMP=`date +"%Y-%m-%d_%H%M%S"` 919 bectl create -r ${VERSION}_${TIMESTAMP} 920 if [ $? -eq 0 ]; then 921 echo "done."; 922 else 923 echo "failed." 924 exit 1 925 fi 926 fi 927 fi 928} 929 930# Perform sanity checks and set some final parameters in 931# preparation for UNinstalling updates. 932rollback_check_params () { 933 # Check that we are root. All sorts of things won't work otherwise. 934 if [ `id -u` != 0 ]; then 935 echo "You must be root to run this." 936 exit 1 937 fi 938 939 # Check that we have a working directory 940 _WORKDIR_bad="Directory does not exist or is not writable: " 941 if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 942 echo -n "`basename $0`: " 943 echo -n "${_WORKDIR_bad}" 944 echo ${WORKDIR} 945 exit 1 946 fi 947 cd ${WORKDIR} || exit 1 948 949 # Construct a unique name from ${BASEDIR} 950 BDHASH=`echo ${BASEDIR} | sha256 -q` 951 952 # Check that we have updates ready to rollback 953 if ! [ -L ${BDHASH}-rollback ]; then 954 echo "No rollback directory found." 955 exit 1 956 fi 957 if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] || 958 ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then 959 echo "Update manifest is corrupt -- this should never happen." 960 exit 1 961 fi 962} 963 964# Perform sanity checks and set some final parameters 965# in preparation for comparing the system against the 966# published index. Figure out which index we should 967# compare against: If the user is running *-p[0-9]+, 968# strip off the last part; if the user is running 969# -SECURITY, call it -RELEASE. Chdir into the working 970# directory. 971IDS_check_params () { 972 export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)" 973 974 _SERVERNAME_z=\ 975"SERVERNAME must be given via command line or configuration file." 976 _KEYPRINT_z="Key must be given via '-k' option or configuration file." 977 _KEYPRINT_bad="Invalid key fingerprint: " 978 _WORKDIR_bad="Directory does not exist or is not writable: " 979 980 if [ -z "${SERVERNAME}" ]; then 981 echo -n "`basename $0`: " 982 echo "${_SERVERNAME_z}" 983 exit 1 984 fi 985 if [ -z "${KEYPRINT}" ]; then 986 echo -n "`basename $0`: " 987 echo "${_KEYPRINT_z}" 988 exit 1 989 fi 990 if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then 991 echo -n "`basename $0`: " 992 echo -n "${_KEYPRINT_bad}" 993 echo ${KEYPRINT} 994 exit 1 995 fi 996 if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then 997 echo -n "`basename $0`: " 998 echo -n "${_WORKDIR_bad}" 999 echo ${WORKDIR} 1000 exit 1 1001 fi 1002 cd ${WORKDIR} || exit 1 1003 1004 # Generate release number. The s/SECURITY/RELEASE/ bit exists 1005 # to provide an upgrade path for FreeBSD Update 1.x users, since 1006 # the kernels provided by FreeBSD Update 1.x are always labelled 1007 # as X.Y-SECURITY. 1008 RELNUM=`uname -r | 1009 sed -E 's,-p[0-9]+,,' | 1010 sed -E 's,-SECURITY,-RELEASE,'` 1011 ARCH=`uname -m` 1012 FETCHDIR=${RELNUM}/${ARCH} 1013 PATCHDIR=${RELNUM}/${ARCH}/bp 1014 1015 # Figure out what directory contains the running kernel 1016 BOOTFILE=`sysctl -n kern.bootfile` 1017 KERNELDIR=${BOOTFILE%/kernel} 1018 if ! [ -d ${KERNELDIR} ]; then 1019 echo "Cannot identify running kernel" 1020 exit 1 1021 fi 1022 1023 # Figure out what kernel configuration is running. We start with 1024 # the output of `uname -i`, and then make the following adjustments: 1025 # 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config 1026 # file says "ident SMP-GENERIC", I don't know... 1027 # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64" 1028 # _and_ `sysctl kern.version` contains a line which ends "/SMP", then 1029 # we're running an SMP kernel. This mis-identification is a bug 1030 # which was fixed in 6.2-STABLE. 1031 KERNCONF=`uname -i` 1032 if [ ${KERNCONF} = "SMP-GENERIC" ]; then 1033 KERNCONF=SMP 1034 fi 1035 if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then 1036 if sysctl kern.version | grep -qE '/SMP$'; then 1037 KERNCONF=SMP 1038 fi 1039 fi 1040 1041 # Define some paths 1042 SHA256=/sbin/sha256 1043 PHTTPGET=/usr/libexec/phttpget 1044 1045 # Set up variables relating to VERBOSELEVEL 1046 fetch_setup_verboselevel 1047} 1048 1049#### Core functionality -- the actual work gets done here 1050 1051# Use an SRV query to pick a server. If the SRV query doesn't provide 1052# a useful answer, use the server name specified by the user. 1053# Put another way... look up _http._tcp.${SERVERNAME} and pick a server 1054# from that; or if no servers are returned, use ${SERVERNAME}. 1055# This allows a user to specify "portsnap.freebsd.org" (in which case 1056# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org" 1057# (in which case portsnap will use that particular server, since there 1058# won't be an SRV entry for that name). 1059# 1060# We ignore the Port field, since we are always going to use port 80. 1061 1062# Fetch the mirror list, but do not pick a mirror yet. Returns 1 if 1063# no mirrors are available for any reason. 1064fetch_pick_server_init () { 1065 : > serverlist_tried 1066 1067# Check that host(1) exists (i.e., that the system wasn't built with the 1068# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist. 1069 if ! which -s host; then 1070 : > serverlist_full 1071 return 1 1072 fi 1073 1074 echo -n "Looking up ${SERVERNAME} mirrors... " 1075 1076# Issue the SRV query and pull out the Priority, Weight, and Target fields. 1077# BIND 9 prints "$name has SRV record ..." while BIND 8 prints 1078# "$name server selection ..."; we allow either format. 1079 MLIST="_http._tcp.${SERVERNAME}" 1080 host -t srv "${MLIST}" | 1081 sed -nE "s/${MLIST} (has SRV record|server selection) //Ip" | 1082 cut -f 1,2,4 -d ' ' | 1083 sed -e 's/\.$//' | 1084 sort > serverlist_full 1085 1086# If no records, give up -- we'll just use the server name we were given. 1087 if [ `wc -l < serverlist_full` -eq 0 ]; then 1088 echo "none found." 1089 return 1 1090 fi 1091 1092# Report how many mirrors we found. 1093 echo `wc -l < serverlist_full` "mirrors found." 1094 1095# Generate a random seed for use in picking mirrors. If HTTP_PROXY 1096# is set, this will be used to generate the seed; otherwise, the seed 1097# will be random. 1098 if [ -n "${HTTP_PROXY}${http_proxy}" ]; then 1099 RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" | 1100 tr -d 'a-f' | 1101 cut -c 1-9` 1102 else 1103 RANDVALUE=`jot -r 1 0 999999999` 1104 fi 1105} 1106 1107# Pick a mirror. Returns 1 if we have run out of mirrors to try. 1108fetch_pick_server () { 1109# Generate a list of not-yet-tried mirrors 1110 sort serverlist_tried | 1111 comm -23 serverlist_full - > serverlist 1112 1113# Have we run out of mirrors? 1114 if [ `wc -l < serverlist` -eq 0 ]; then 1115 cat <<- EOF 1116 No mirrors remaining, giving up. 1117 1118 This may be because upgrading from this platform (${ARCH}) 1119 or release (${RELNUM}) is unsupported by `basename $0`. Only 1120 platforms with Tier 1 support can be upgraded by `basename $0`. 1121 See https://www.freebsd.org/platforms/ for more info. 1122 1123 If unsupported, FreeBSD must be upgraded by source. 1124 EOF 1125 return 1 1126 fi 1127 1128# Find the highest priority level (lowest numeric value). 1129 SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1` 1130 1131# Add up the weights of the response lines at that priority level. 1132 SRV_WSUM=0; 1133 while read X; do 1134 case "$X" in 1135 ${SRV_PRIORITY}\ *) 1136 SRV_W=`echo $X | cut -f 2 -d ' '` 1137 SRV_WSUM=$(($SRV_WSUM + $SRV_W)) 1138 ;; 1139 esac 1140 done < serverlist 1141 1142# If all the weights are 0, pretend that they are all 1 instead. 1143 if [ ${SRV_WSUM} -eq 0 ]; then 1144 SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l` 1145 SRV_W_ADD=1 1146 else 1147 SRV_W_ADD=0 1148 fi 1149 1150# Pick a value between 0 and the sum of the weights - 1 1151 SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}` 1152 1153# Read through the list of mirrors and set SERVERNAME. Write the line 1154# corresponding to the mirror we selected into serverlist_tried so that 1155# we won't try it again. 1156 while read X; do 1157 case "$X" in 1158 ${SRV_PRIORITY}\ *) 1159 SRV_W=`echo $X | cut -f 2 -d ' '` 1160 SRV_W=$(($SRV_W + $SRV_W_ADD)) 1161 if [ $SRV_RND -lt $SRV_W ]; then 1162 SERVERNAME=`echo $X | cut -f 3 -d ' '` 1163 echo "$X" >> serverlist_tried 1164 break 1165 else 1166 SRV_RND=$(($SRV_RND - $SRV_W)) 1167 fi 1168 ;; 1169 esac 1170 done < serverlist 1171} 1172 1173# Take a list of ${oldhash}|${newhash} and output a list of needed patches, 1174# i.e., those for which we have ${oldhash} and don't have ${newhash}. 1175fetch_make_patchlist () { 1176 grep -vE "^([0-9a-f]{64})\|\1$" | 1177 tr '|' ' ' | 1178 while read X Y; do 1179 if [ -f "files/${Y}.gz" ] || 1180 [ ! -f "files/${X}.gz" ]; then 1181 continue 1182 fi 1183 echo "${X}|${Y}" 1184 done | sort -u 1185} 1186 1187# Print user-friendly progress statistics 1188fetch_progress () { 1189 LNC=0 1190 while read x; do 1191 LNC=$(($LNC + 1)) 1192 if [ $(($LNC % 10)) = 0 ]; then 1193 echo -n $LNC 1194 elif [ $(($LNC % 2)) = 0 ]; then 1195 echo -n . 1196 fi 1197 done 1198 echo -n " " 1199} 1200 1201# Function for asking the user if everything is ok 1202continuep () { 1203 while read -p "Does this look reasonable (y/n)? " CONTINUE; do 1204 case "${CONTINUE}" in 1205 [yY]*) 1206 return 0 1207 ;; 1208 [nN]*) 1209 return 1 1210 ;; 1211 esac 1212 done 1213} 1214 1215# Initialize the working directory 1216workdir_init () { 1217 mkdir -p files 1218 touch tINDEX.present 1219} 1220 1221# Check that we have a public key with an appropriate hash, or 1222# fetch the key if it doesn't exist. Returns 1 if the key has 1223# not yet been fetched. 1224fetch_key () { 1225 if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then 1226 return 0 1227 fi 1228 1229 echo -n "Fetching public key from ${SERVERNAME}... " 1230 rm -f pub.ssl 1231 fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \ 1232 2>${QUIETREDIR} || true 1233 if ! [ -r pub.ssl ]; then 1234 echo "failed." 1235 return 1 1236 fi 1237 if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then 1238 echo "key has incorrect hash." 1239 rm -f pub.ssl 1240 return 1 1241 fi 1242 echo "done." 1243} 1244 1245# Fetch metadata signature, aka "tag". 1246fetch_tag () { 1247 echo -n "Fetching metadata signature " 1248 echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... " 1249 rm -f latest.ssl 1250 fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl \ 1251 2>${QUIETREDIR} || true 1252 if ! [ -r latest.ssl ]; then 1253 echo "failed." 1254 return 1 1255 fi 1256 1257 openssl rsautl -pubin -inkey pub.ssl -verify \ 1258 < latest.ssl > tag.new 2>${QUIETREDIR} || true 1259 rm latest.ssl 1260 1261 if ! [ `wc -l < tag.new` = 1 ] || 1262 ! grep -qE \ 1263 "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \ 1264 tag.new; then 1265 echo "invalid signature." 1266 return 1 1267 fi 1268 1269 echo "done." 1270 1271 RELPATCHNUM=`cut -f 4 -d '|' < tag.new` 1272 TINDEXHASH=`cut -f 5 -d '|' < tag.new` 1273 EOLTIME=`cut -f 6 -d '|' < tag.new` 1274} 1275 1276# Sanity-check the patch number in a tag, to make sure that we're not 1277# going to "update" backwards and to prevent replay attacks. 1278fetch_tagsanity () { 1279 # Check that we're not going to move from -pX to -pY with Y < X. 1280 RELPX=`uname -r | sed -E 's,.*-,,'` 1281 if echo ${RELPX} | grep -qE '^p[0-9]+$'; then 1282 RELPX=`echo ${RELPX} | cut -c 2-` 1283 else 1284 RELPX=0 1285 fi 1286 if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then 1287 echo 1288 echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})" 1289 echo " appear older than what" 1290 echo "we are currently running (`uname -r`)!" 1291 echo "Cowardly refusing to proceed any further." 1292 return 1 1293 fi 1294 1295 # If "tag" exists and corresponds to ${RELNUM}, make sure that 1296 # it contains a patch number <= RELPATCHNUM, in order to protect 1297 # against rollback (replay) attacks. 1298 if [ -f tag ] && 1299 grep -qE \ 1300 "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \ 1301 tag; then 1302 LASTRELPATCHNUM=`cut -f 4 -d '|' < tag` 1303 1304 if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then 1305 echo 1306 echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})" 1307 echo " are older than the" 1308 echo -n "most recently seen updates" 1309 echo " (${RELNUM}-p${LASTRELPATCHNUM})." 1310 echo "Cowardly refusing to proceed any further." 1311 return 1 1312 fi 1313 fi 1314} 1315 1316# Fetch metadata index file 1317fetch_metadata_index () { 1318 echo ${NDEBUG} "Fetching metadata index... " 1319 rm -f ${TINDEXHASH} 1320 fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH} 1321 2>${QUIETREDIR} 1322 if ! [ -f ${TINDEXHASH} ]; then 1323 echo "failed." 1324 return 1 1325 fi 1326 if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then 1327 echo "update metadata index corrupt." 1328 return 1 1329 fi 1330 echo "done." 1331} 1332 1333# Print an error message about signed metadata being bogus. 1334fetch_metadata_bogus () { 1335 echo 1336 echo "The update metadata$1 is correctly signed, but" 1337 echo "failed an integrity check." 1338 echo "Cowardly refusing to proceed any further." 1339 return 1 1340} 1341 1342# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH} 1343# with the lines not named in $@ from tINDEX.present (if that file exists). 1344fetch_metadata_index_merge () { 1345 for METAFILE in $@; do 1346 if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l` \ 1347 -ne 1 ]; then 1348 fetch_metadata_bogus " index" 1349 return 1 1350 fi 1351 1352 grep -E "${METAFILE}\|" ${TINDEXHASH} 1353 done | 1354 sort > tINDEX.wanted 1355 1356 if [ -f tINDEX.present ]; then 1357 join -t '|' -v 2 tINDEX.wanted tINDEX.present | 1358 sort -m - tINDEX.wanted > tINDEX.new 1359 rm tINDEX.wanted 1360 else 1361 mv tINDEX.wanted tINDEX.new 1362 fi 1363} 1364 1365# Sanity check all the lines of tINDEX.new. Even if more metadata lines 1366# are added by future versions of the server, this won't cause problems, 1367# since the only lines which appear in tINDEX.new are the ones which we 1368# specifically grepped out of ${TINDEXHASH}. 1369fetch_metadata_index_sanity () { 1370 if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then 1371 fetch_metadata_bogus " index" 1372 return 1 1373 fi 1374} 1375 1376# Sanity check the metadata file $1. 1377fetch_metadata_sanity () { 1378 # Some aliases to save space later: ${P} is a character which can 1379 # appear in a path; ${M} is the four numeric metadata fields; and 1380 # ${H} is a sha256 hash. 1381 P="[-+./:=,%@_[~[:alnum:]]" 1382 M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+" 1383 H="[0-9a-f]{64}" 1384 1385 # Check that the first four fields make sense. 1386 if gunzip -c < files/$1.gz | 1387 grep -qvE "^[a-z]+\|[0-9a-z-]+\|${P}+\|[fdL-]\|"; then 1388 fetch_metadata_bogus "" 1389 return 1 1390 fi 1391 1392 # Remove the first three fields. 1393 gunzip -c < files/$1.gz | 1394 cut -f 4- -d '|' > sanitycheck.tmp 1395 1396 # Sanity check entries with type 'f' 1397 if grep -E '^f' sanitycheck.tmp | 1398 grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then 1399 fetch_metadata_bogus "" 1400 return 1 1401 fi 1402 1403 # Sanity check entries with type 'd' 1404 if grep -E '^d' sanitycheck.tmp | 1405 grep -qvE "^d\|${M}\|\|\$"; then 1406 fetch_metadata_bogus "" 1407 return 1 1408 fi 1409 1410 # Sanity check entries with type 'L' 1411 if grep -E '^L' sanitycheck.tmp | 1412 grep -qvE "^L\|${M}\|${P}*\|\$"; then 1413 fetch_metadata_bogus "" 1414 return 1 1415 fi 1416 1417 # Sanity check entries with type '-' 1418 if grep -E '^-' sanitycheck.tmp | 1419 grep -qvE "^-\|\|\|\|\|\|"; then 1420 fetch_metadata_bogus "" 1421 return 1 1422 fi 1423 1424 # Clean up 1425 rm sanitycheck.tmp 1426} 1427 1428# Fetch the metadata index and metadata files listed in $@, 1429# taking advantage of metadata patches where possible. 1430fetch_metadata () { 1431 fetch_metadata_index || return 1 1432 fetch_metadata_index_merge $@ || return 1 1433 fetch_metadata_index_sanity || return 1 1434 1435 # Generate a list of wanted metadata patches 1436 join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new | 1437 fetch_make_patchlist > patchlist 1438 1439 if [ -s patchlist ]; then 1440 # Attempt to fetch metadata patches 1441 echo -n "Fetching `wc -l < patchlist | tr -d ' '` " 1442 echo ${NDEBUG} "metadata patches.${DDSTATS}" 1443 tr '|' '-' < patchlist | 1444 lam -s "${FETCHDIR}/tp/" - -s ".gz" | 1445 xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1446 2>${STATSREDIR} | fetch_progress 1447 echo "done." 1448 1449 # Attempt to apply metadata patches 1450 echo -n "Applying metadata patches... " 1451 tr '|' ' ' < patchlist | 1452 while read X Y; do 1453 if [ ! -f "${X}-${Y}.gz" ]; then continue; fi 1454 gunzip -c < ${X}-${Y}.gz > diff 1455 gunzip -c < files/${X}.gz > diff-OLD 1456 1457 # Figure out which lines are being added and removed 1458 grep -E '^-' diff | 1459 cut -c 2- | 1460 while read PREFIX; do 1461 look "${PREFIX}" diff-OLD 1462 done | 1463 sort > diff-rm 1464 grep -E '^\+' diff | 1465 cut -c 2- > diff-add 1466 1467 # Generate the new file 1468 comm -23 diff-OLD diff-rm | 1469 sort - diff-add > diff-NEW 1470 1471 if [ `${SHA256} -q diff-NEW` = ${Y} ]; then 1472 mv diff-NEW files/${Y} 1473 gzip -n files/${Y} 1474 else 1475 mv diff-NEW ${Y}.bad 1476 fi 1477 rm -f ${X}-${Y}.gz diff 1478 rm -f diff-OLD diff-NEW diff-add diff-rm 1479 done 2>${QUIETREDIR} 1480 echo "done." 1481 fi 1482 1483 # Update metadata without patches 1484 cut -f 2 -d '|' < tINDEX.new | 1485 while read Y; do 1486 if [ ! -f "files/${Y}.gz" ]; then 1487 echo ${Y}; 1488 fi 1489 done | 1490 sort -u > filelist 1491 1492 if [ -s filelist ]; then 1493 echo -n "Fetching `wc -l < filelist | tr -d ' '` " 1494 echo ${NDEBUG} "metadata files... " 1495 lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist | 1496 xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1497 2>${QUIETREDIR} 1498 1499 while read Y; do 1500 if ! [ -f ${Y}.gz ]; then 1501 echo "failed." 1502 return 1 1503 fi 1504 if [ `gunzip -c < ${Y}.gz | 1505 ${SHA256} -q` = ${Y} ]; then 1506 mv ${Y}.gz files/${Y}.gz 1507 else 1508 echo "metadata is corrupt." 1509 return 1 1510 fi 1511 done < filelist 1512 echo "done." 1513 fi 1514 1515# Sanity-check the metadata files. 1516 cut -f 2 -d '|' tINDEX.new > filelist 1517 while read X; do 1518 fetch_metadata_sanity ${X} || return 1 1519 done < filelist 1520 1521# Remove files which are no longer needed 1522 cut -f 2 -d '|' tINDEX.present | 1523 sort > oldfiles 1524 cut -f 2 -d '|' tINDEX.new | 1525 sort | 1526 comm -13 - oldfiles | 1527 lam -s "files/" - -s ".gz" | 1528 xargs rm -f 1529 rm patchlist filelist oldfiles 1530 rm ${TINDEXHASH} 1531 1532# We're done! 1533 mv tINDEX.new tINDEX.present 1534 mv tag.new tag 1535 1536 return 0 1537} 1538 1539# Extract a subset of a downloaded metadata file containing only the parts 1540# which are listed in COMPONENTS. 1541fetch_filter_metadata_components () { 1542 METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'` 1543 gunzip -c < files/${METAHASH}.gz > $1.all 1544 1545 # Fish out the lines belonging to components we care about. 1546 for C in ${COMPONENTS}; do 1547 look "`echo ${C} | tr '/' '|'`|" $1.all 1548 done > $1 1549 1550 # Remove temporary file. 1551 rm $1.all 1552} 1553 1554# Generate a filtered version of the metadata file $1 from the downloaded 1555# file, by fishing out the lines corresponding to components we're trying 1556# to keep updated, and then removing lines corresponding to paths we want 1557# to ignore. 1558fetch_filter_metadata () { 1559 # Fish out the lines belonging to components we care about. 1560 fetch_filter_metadata_components $1 1561 1562 # Canonicalize directory names by removing any trailing / in 1563 # order to avoid listing directories multiple times if they 1564 # belong to multiple components. Turning "/" into "" doesn't 1565 # matter, since we add a leading "/" when we use paths later. 1566 cut -f 3- -d '|' $1 | 1567 sed -e 's,/|d|,|d|,' | 1568 sed -e 's,/|-|,|-|,' | 1569 sort -u > $1.tmp 1570 1571 # Figure out which lines to ignore and remove them. 1572 for X in ${IGNOREPATHS}; do 1573 grep -E "^${X}" $1.tmp 1574 done | 1575 sort -u | 1576 comm -13 - $1.tmp > $1 1577 1578 # Remove temporary files. 1579 rm $1.tmp 1580} 1581 1582# Filter the metadata file $1 by adding lines with "/boot/$2" 1583# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the 1584# trailing "/kernel"); and if "/boot/$2" does not exist, remove 1585# the original lines which start with that. 1586# Put another way: Deal with the fact that the FOO kernel is sometimes 1587# installed in /boot/FOO/ and is sometimes installed elsewhere. 1588fetch_filter_kernel_names () { 1589 grep ^/boot/$2 $1 | 1590 sed -e "s,/boot/$2,${KERNELDIR},g" | 1591 sort - $1 > $1.tmp 1592 mv $1.tmp $1 1593 1594 if ! [ -d /boot/$2 ]; then 1595 grep -v ^/boot/$2 $1 > $1.tmp 1596 mv $1.tmp $1 1597 fi 1598} 1599 1600# For all paths appearing in $1 or $3, inspect the system 1601# and generate $2 describing what is currently installed. 1602fetch_inspect_system () { 1603 # No errors yet... 1604 rm -f .err 1605 1606 # Tell the user why his disk is suddenly making lots of noise 1607 echo -n "Inspecting system... " 1608 1609 # Generate list of files to inspect 1610 cat $1 $3 | 1611 cut -f 1 -d '|' | 1612 sort -u > filelist 1613 1614 # Examine each file and output lines of the form 1615 # /path/to/file|type|device-inum|user|group|perm|flags|value 1616 # sorted by device and inode number. 1617 while read F; do 1618 # If the symlink/file/directory does not exist, record this. 1619 if ! [ -e ${BASEDIR}/${F} ]; then 1620 echo "${F}|-||||||" 1621 continue 1622 fi 1623 if ! [ -r ${BASEDIR}/${F} ]; then 1624 echo "Cannot read file: ${BASEDIR}/${F}" \ 1625 >/dev/stderr 1626 touch .err 1627 return 1 1628 fi 1629 1630 # Otherwise, output an index line. 1631 if [ -L ${BASEDIR}/${F} ]; then 1632 echo -n "${F}|L|" 1633 stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1634 readlink ${BASEDIR}/${F}; 1635 elif [ -f ${BASEDIR}/${F} ]; then 1636 echo -n "${F}|f|" 1637 stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1638 sha256 -q ${BASEDIR}/${F}; 1639 elif [ -d ${BASEDIR}/${F} ]; then 1640 echo -n "${F}|d|" 1641 stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F}; 1642 else 1643 echo "Unknown file type: ${BASEDIR}/${F}" \ 1644 >/dev/stderr 1645 touch .err 1646 return 1 1647 fi 1648 done < filelist | 1649 sort -k 3,3 -t '|' > $2.tmp 1650 rm filelist 1651 1652 # Check if an error occurred during system inspection 1653 if [ -f .err ]; then 1654 return 1 1655 fi 1656 1657 # Convert to the form 1658 # /path/to/file|type|user|group|perm|flags|value|hlink 1659 # by resolving identical device and inode numbers into hard links. 1660 cut -f 1,3 -d '|' $2.tmp | 1661 sort -k 1,1 -t '|' | 1662 sort -s -u -k 2,2 -t '|' | 1663 join -1 2 -2 3 -t '|' - $2.tmp | 1664 awk -F \| -v OFS=\| \ 1665 '{ 1666 if (($2 == $3) || ($4 == "-")) 1667 print $3,$4,$5,$6,$7,$8,$9,"" 1668 else 1669 print $3,$4,$5,$6,$7,$8,$9,$2 1670 }' | 1671 sort > $2 1672 rm $2.tmp 1673 1674 # We're finished looking around 1675 echo "done." 1676} 1677 1678# For any paths matching ${MERGECHANGES}, compare $2 against $1 and $3 and 1679# find any files with values unique to $2; generate $4 containing these paths 1680# and their corresponding hashes from $1. 1681fetch_filter_mergechanges () { 1682 # Pull out the paths and hashes of the files matching ${MERGECHANGES}. 1683 for F in $1 $2 $3; do 1684 for X in ${MERGECHANGES}; do 1685 grep -E "^${X}" ${F} 1686 done | 1687 cut -f 1,2,7 -d '|' | 1688 sort > ${F}-values 1689 done 1690 1691 # Any line in $2-values which doesn't appear in $1-values or $3-values 1692 # and is a file means that we should list the path in $3. 1693 sort $1-values $3-values | 1694 comm -13 - $2-values | 1695 fgrep '|f|' | 1696 cut -f 1 -d '|' > $2-paths 1697 1698 # For each path, pull out one (and only one!) entry from $1-values. 1699 # Note that we cannot distinguish which "old" version the user made 1700 # changes to; but hopefully any changes which occur due to security 1701 # updates will exist in both the "new" version and the version which 1702 # the user has installed, so the merging will still work. 1703 while read X; do 1704 look "${X}|" $1-values | 1705 head -1 1706 done < $2-paths > $4 1707 1708 # Clean up 1709 rm $1-values $2-values $3-values $2-paths 1710} 1711 1712# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123] 1713# which correspond to lines in $2 with hashes not matching $1 or $3, unless 1714# the paths are listed in $4. For entries in $2 marked "not present" 1715# (aka. type -), remove lines from $[123] unless there is a corresponding 1716# entry in $1. 1717fetch_filter_unmodified_notpresent () { 1718 # Figure out which lines of $1 and $3 correspond to bits which 1719 # should only be updated if they haven't changed, and fish out 1720 # the (path, type, value) tuples. 1721 # NOTE: We don't consider a file to be "modified" if it matches 1722 # the hash from $3. 1723 for X in ${UPDATEIFUNMODIFIED}; do 1724 grep -E "^${X}" $1 1725 grep -E "^${X}" $3 1726 done | 1727 cut -f 1,2,7 -d '|' | 1728 sort > $1-values 1729 1730 # Do the same for $2. 1731 for X in ${UPDATEIFUNMODIFIED}; do 1732 grep -E "^${X}" $2 1733 done | 1734 cut -f 1,2,7 -d '|' | 1735 sort > $2-values 1736 1737 # Any entry in $2-values which is not in $1-values corresponds to 1738 # a path which we need to remove from $1, $2, and $3, unless it 1739 # that path appears in $4. 1740 comm -13 $1-values $2-values | 1741 sort -t '|' -k 1,1 > mlines.tmp 1742 cut -f 1 -d '|' $4 | 1743 sort | 1744 join -v 2 -t '|' - mlines.tmp | 1745 sort > mlines 1746 rm $1-values $2-values mlines.tmp 1747 1748 # Any lines in $2 which are not in $1 AND are "not present" lines 1749 # also belong in mlines. 1750 comm -13 $1 $2 | 1751 cut -f 1,2,7 -d '|' | 1752 fgrep '|-|' >> mlines 1753 1754 # Remove lines from $1, $2, and $3 1755 for X in $1 $2 $3; do 1756 sort -t '|' -k 1,1 ${X} > ${X}.tmp 1757 cut -f 1 -d '|' < mlines | 1758 sort | 1759 join -v 2 -t '|' - ${X}.tmp | 1760 sort > ${X} 1761 rm ${X}.tmp 1762 done 1763 1764 # Store a list of the modified files, for future reference 1765 fgrep -v '|-|' mlines | 1766 cut -f 1 -d '|' > modifiedfiles 1767 rm mlines 1768} 1769 1770# For each entry in $1 of type -, remove any corresponding 1771# entry from $2 if ${ALLOWADD} != "yes". Remove all entries 1772# of type - from $1. 1773fetch_filter_allowadd () { 1774 cut -f 1,2 -d '|' < $1 | 1775 fgrep '|-' | 1776 cut -f 1 -d '|' > filesnotpresent 1777 1778 if [ ${ALLOWADD} != "yes" ]; then 1779 sort < $2 | 1780 join -v 1 -t '|' - filesnotpresent | 1781 sort > $2.tmp 1782 mv $2.tmp $2 1783 fi 1784 1785 sort < $1 | 1786 join -v 1 -t '|' - filesnotpresent | 1787 sort > $1.tmp 1788 mv $1.tmp $1 1789 rm filesnotpresent 1790} 1791 1792# If ${ALLOWDELETE} != "yes", then remove any entries from $1 1793# which don't correspond to entries in $2. 1794fetch_filter_allowdelete () { 1795 # Produce a lists ${PATH}|${TYPE} 1796 for X in $1 $2; do 1797 cut -f 1-2 -d '|' < ${X} | 1798 sort -u > ${X}.nodes 1799 done 1800 1801 # Figure out which lines need to be removed from $1. 1802 if [ ${ALLOWDELETE} != "yes" ]; then 1803 comm -23 $1.nodes $2.nodes > $1.badnodes 1804 else 1805 : > $1.badnodes 1806 fi 1807 1808 # Remove the relevant lines from $1 1809 while read X; do 1810 look "${X}|" $1 1811 done < $1.badnodes | 1812 comm -13 - $1 > $1.tmp 1813 mv $1.tmp $1 1814 1815 rm $1.badnodes $1.nodes $2.nodes 1816} 1817 1818# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2 1819# with metadata not matching any entry in $1, replace the corresponding 1820# line of $3 with one having the same metadata as the entry in $2. 1821fetch_filter_modified_metadata () { 1822 # Fish out the metadata from $1 and $2 1823 for X in $1 $2; do 1824 cut -f 1-6 -d '|' < ${X} > ${X}.metadata 1825 done 1826 1827 # Find the metadata we need to keep 1828 if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then 1829 comm -13 $1.metadata $2.metadata > keepmeta 1830 else 1831 : > keepmeta 1832 fi 1833 1834 # Extract the lines which we need to remove from $3, and 1835 # construct the lines which we need to add to $3. 1836 : > $3.remove 1837 : > $3.add 1838 while read LINE; do 1839 NODE=`echo "${LINE}" | cut -f 1-2 -d '|'` 1840 look "${NODE}|" $3 >> $3.remove 1841 look "${NODE}|" $3 | 1842 cut -f 7- -d '|' | 1843 lam -s "${LINE}|" - >> $3.add 1844 done < keepmeta 1845 1846 # Remove the specified lines and add the new lines. 1847 sort $3.remove | 1848 comm -13 - $3 | 1849 sort -u - $3.add > $3.tmp 1850 mv $3.tmp $3 1851 1852 rm keepmeta $1.metadata $2.metadata $3.add $3.remove 1853} 1854 1855# Remove lines from $1 and $2 which are identical; 1856# no need to update a file if it isn't changing. 1857fetch_filter_uptodate () { 1858 comm -23 $1 $2 > $1.tmp 1859 comm -13 $1 $2 > $2.tmp 1860 1861 mv $1.tmp $1 1862 mv $2.tmp $2 1863} 1864 1865# Fetch any "clean" old versions of files we need for merging changes. 1866fetch_files_premerge () { 1867 # We only need to do anything if $1 is non-empty. 1868 if [ -s $1 ]; then 1869 # Tell the user what we're doing 1870 echo -n "Fetching files from ${OLDRELNUM} for merging... " 1871 1872 # List of files wanted 1873 fgrep '|f|' < $1 | 1874 cut -f 3 -d '|' | 1875 sort -u > files.wanted 1876 1877 # Only fetch the files we don't already have 1878 while read Y; do 1879 if [ ! -f "files/${Y}.gz" ]; then 1880 echo ${Y}; 1881 fi 1882 done < files.wanted > filelist 1883 1884 # Actually fetch them 1885 lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist | 1886 xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1887 2>${QUIETREDIR} 1888 1889 # Make sure we got them all, and move them into /files/ 1890 while read Y; do 1891 if ! [ -f ${Y}.gz ]; then 1892 echo "failed." 1893 return 1 1894 fi 1895 if [ `gunzip -c < ${Y}.gz | 1896 ${SHA256} -q` = ${Y} ]; then 1897 mv ${Y}.gz files/${Y}.gz 1898 else 1899 echo "${Y} has incorrect hash." 1900 return 1 1901 fi 1902 done < filelist 1903 echo "done." 1904 1905 # Clean up 1906 rm filelist files.wanted 1907 fi 1908} 1909 1910# Prepare to fetch files: Generate a list of the files we need, 1911# copy the unmodified files we have into /files/, and generate 1912# a list of patches to download. 1913fetch_files_prepare () { 1914 # Tell the user why his disk is suddenly making lots of noise 1915 echo -n "Preparing to download files... " 1916 1917 # Reduce indices to ${PATH}|${HASH} pairs 1918 for X in $1 $2 $3; do 1919 cut -f 1,2,7 -d '|' < ${X} | 1920 fgrep '|f|' | 1921 cut -f 1,3 -d '|' | 1922 sort > ${X}.hashes 1923 done 1924 1925 # List of files wanted 1926 cut -f 2 -d '|' < $3.hashes | 1927 sort -u | 1928 while read HASH; do 1929 if ! [ -f files/${HASH}.gz ]; then 1930 echo ${HASH} 1931 fi 1932 done > files.wanted 1933 1934 # Generate a list of unmodified files 1935 comm -12 $1.hashes $2.hashes | 1936 sort -k 1,1 -t '|' > unmodified.files 1937 1938 # Copy all files into /files/. We only need the unmodified files 1939 # for use in patching; but we'll want all of them if the user asks 1940 # to rollback the updates later. 1941 while read LINE; do 1942 F=`echo "${LINE}" | cut -f 1 -d '|'` 1943 HASH=`echo "${LINE}" | cut -f 2 -d '|'` 1944 1945 # Skip files we already have. 1946 if [ -f files/${HASH}.gz ]; then 1947 continue 1948 fi 1949 1950 # Make sure the file hasn't changed. 1951 cp "${BASEDIR}/${F}" tmpfile 1952 if [ `sha256 -q tmpfile` != ${HASH} ]; then 1953 echo 1954 echo "File changed while FreeBSD Update running: ${F}" 1955 return 1 1956 fi 1957 1958 # Place the file into storage. 1959 gzip -c < tmpfile > files/${HASH}.gz 1960 rm tmpfile 1961 done < $2.hashes 1962 1963 # Produce a list of patches to download 1964 sort -k 1,1 -t '|' $3.hashes | 1965 join -t '|' -o 2.2,1.2 - unmodified.files | 1966 fetch_make_patchlist > patchlist 1967 1968 # Garbage collect 1969 rm unmodified.files $1.hashes $2.hashes $3.hashes 1970 1971 # We don't need the list of possible old files any more. 1972 rm $1 1973 1974 # We're finished making noise 1975 echo "done." 1976} 1977 1978# Fetch files. 1979fetch_files () { 1980 # Attempt to fetch patches 1981 if [ -s patchlist ]; then 1982 echo -n "Fetching `wc -l < patchlist | tr -d ' '` " 1983 echo ${NDEBUG} "patches.${DDSTATS}" 1984 tr '|' '-' < patchlist | 1985 lam -s "${PATCHDIR}/" - | 1986 xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 1987 2>${STATSREDIR} | fetch_progress 1988 echo "done." 1989 1990 # Attempt to apply patches 1991 echo -n "Applying patches... " 1992 tr '|' ' ' < patchlist | 1993 while read X Y; do 1994 if [ ! -f "${X}-${Y}" ]; then continue; fi 1995 gunzip -c < files/${X}.gz > OLD 1996 1997 bspatch OLD NEW ${X}-${Y} 1998 1999 if [ `${SHA256} -q NEW` = ${Y} ]; then 2000 mv NEW files/${Y} 2001 gzip -n files/${Y} 2002 fi 2003 rm -f diff OLD NEW ${X}-${Y} 2004 done 2>${QUIETREDIR} 2005 echo "done." 2006 fi 2007 2008 # Download files which couldn't be generate via patching 2009 while read Y; do 2010 if [ ! -f "files/${Y}.gz" ]; then 2011 echo ${Y}; 2012 fi 2013 done < files.wanted > filelist 2014 2015 if [ -s filelist ]; then 2016 echo -n "Fetching `wc -l < filelist | tr -d ' '` " 2017 echo ${NDEBUG} "files... " 2018 lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist | 2019 xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \ 2020 2>${STATSREDIR} | fetch_progress 2021 2022 while read Y; do 2023 if ! [ -f ${Y}.gz ]; then 2024 echo "failed." 2025 return 1 2026 fi 2027 if [ `gunzip -c < ${Y}.gz | 2028 ${SHA256} -q` = ${Y} ]; then 2029 mv ${Y}.gz files/${Y}.gz 2030 else 2031 echo "${Y} has incorrect hash." 2032 return 1 2033 fi 2034 done < filelist 2035 echo "done." 2036 fi 2037 2038 # Clean up 2039 rm files.wanted filelist patchlist 2040} 2041 2042# Create and populate install manifest directory; and report what updates 2043# are available. 2044fetch_create_manifest () { 2045 # If we have an existing install manifest, nuke it. 2046 if [ -L "${BDHASH}-install" ]; then 2047 rm -r ${BDHASH}-install/ 2048 rm ${BDHASH}-install 2049 fi 2050 2051 # Report to the user if any updates were avoided due to local changes 2052 if [ -s modifiedfiles ]; then 2053 cat - modifiedfiles <<- EOF | ${PAGER} 2054 The following files are affected by updates. No changes have 2055 been downloaded, however, because the files have been modified 2056 locally: 2057 EOF 2058 fi 2059 rm modifiedfiles 2060 2061 # If no files will be updated, tell the user and exit 2062 if ! [ -s INDEX-PRESENT ] && 2063 ! [ -s INDEX-NEW ]; then 2064 rm INDEX-PRESENT INDEX-NEW 2065 echo 2066 echo -n "No updates needed to update system to " 2067 echo "${RELNUM}-p${RELPATCHNUM}." 2068 return 2069 fi 2070 2071 # Divide files into (a) removed files, (b) added files, and 2072 # (c) updated files. 2073 cut -f 1 -d '|' < INDEX-PRESENT | 2074 sort > INDEX-PRESENT.flist 2075 cut -f 1 -d '|' < INDEX-NEW | 2076 sort > INDEX-NEW.flist 2077 comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed 2078 comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added 2079 comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated 2080 rm INDEX-PRESENT.flist INDEX-NEW.flist 2081 2082 # Report removed files, if any 2083 if [ -s files.removed ]; then 2084 cat - files.removed <<- EOF | ${PAGER} 2085 The following files will be removed as part of updating to 2086 ${RELNUM}-p${RELPATCHNUM}: 2087 EOF 2088 fi 2089 rm files.removed 2090 2091 # Report added files, if any 2092 if [ -s files.added ]; then 2093 cat - files.added <<- EOF | ${PAGER} 2094 The following files will be added as part of updating to 2095 ${RELNUM}-p${RELPATCHNUM}: 2096 EOF 2097 fi 2098 rm files.added 2099 2100 # Report updated files, if any 2101 if [ -s files.updated ]; then 2102 cat - files.updated <<- EOF | ${PAGER} 2103 The following files will be updated as part of updating to 2104 ${RELNUM}-p${RELPATCHNUM}: 2105 EOF 2106 fi 2107 rm files.updated 2108 2109 # Create a directory for the install manifest. 2110 MDIR=`mktemp -d install.XXXXXX` || return 1 2111 2112 # Populate it 2113 mv INDEX-PRESENT ${MDIR}/INDEX-OLD 2114 mv INDEX-NEW ${MDIR}/INDEX-NEW 2115 2116 # Link it into place 2117 ln -s ${MDIR} ${BDHASH}-install 2118} 2119 2120# Warn about any upcoming EoL 2121fetch_warn_eol () { 2122 # What's the current time? 2123 NOWTIME=`date "+%s"` 2124 2125 # When did we last warn about the EoL date? 2126 if [ -f lasteolwarn ]; then 2127 LASTWARN=`cat lasteolwarn` 2128 else 2129 LASTWARN=`expr ${NOWTIME} - 63072000` 2130 fi 2131 2132 # If the EoL time is past, warn. 2133 if [ ${EOLTIME} -lt ${NOWTIME} ]; then 2134 echo 2135 cat <<-EOF 2136 WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE. 2137 Any security issues discovered after `date -r ${EOLTIME}` 2138 will not have been corrected. 2139 EOF 2140 return 1 2141 fi 2142 2143 # Figure out how long it has been since we last warned about the 2144 # upcoming EoL, and how much longer we have left. 2145 SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}` 2146 TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}` 2147 2148 # Don't warn if the EoL is more than 3 months away 2149 if [ ${TIMELEFT} -gt 7884000 ]; then 2150 return 0 2151 fi 2152 2153 # Don't warn if the time remaining is more than 3 times the time 2154 # since the last warning. 2155 if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then 2156 return 0 2157 fi 2158 2159 # Figure out what time units to use. 2160 if [ ${TIMELEFT} -lt 604800 ]; then 2161 UNIT="day" 2162 SIZE=86400 2163 elif [ ${TIMELEFT} -lt 2678400 ]; then 2164 UNIT="week" 2165 SIZE=604800 2166 else 2167 UNIT="month" 2168 SIZE=2678400 2169 fi 2170 2171 # Compute the right number of units 2172 NUM=`expr ${TIMELEFT} / ${SIZE}` 2173 if [ ${NUM} != 1 ]; then 2174 UNIT="${UNIT}s" 2175 fi 2176 2177 # Print the warning 2178 echo 2179 cat <<-EOF 2180 WARNING: `uname -sr` is approaching its End-of-Life date. 2181 It is strongly recommended that you upgrade to a newer 2182 release within the next ${NUM} ${UNIT}. 2183 EOF 2184 2185 # Update the stored time of last warning 2186 echo ${NOWTIME} > lasteolwarn 2187} 2188 2189# Do the actual work involved in "fetch" / "cron". 2190fetch_run () { 2191 workdir_init || return 1 2192 2193 # Prepare the mirror list. 2194 fetch_pick_server_init && fetch_pick_server 2195 2196 # Try to fetch the public key until we run out of servers. 2197 while ! fetch_key; do 2198 fetch_pick_server || return 1 2199 done 2200 2201 # Try to fetch the metadata index signature ("tag") until we run 2202 # out of available servers; and sanity check the downloaded tag. 2203 while ! fetch_tag; do 2204 fetch_pick_server || return 1 2205 done 2206 fetch_tagsanity || return 1 2207 2208 # Fetch the latest INDEX-NEW and INDEX-OLD files. 2209 fetch_metadata INDEX-NEW INDEX-OLD || return 1 2210 2211 # Generate filtered INDEX-NEW and INDEX-OLD files containing only 2212 # the lines which (a) belong to components we care about, and (b) 2213 # don't correspond to paths we're explicitly ignoring. 2214 fetch_filter_metadata INDEX-NEW || return 1 2215 fetch_filter_metadata INDEX-OLD || return 1 2216 2217 # Translate /boot/${KERNCONF} into ${KERNELDIR} 2218 fetch_filter_kernel_names INDEX-NEW ${KERNCONF} 2219 fetch_filter_kernel_names INDEX-OLD ${KERNCONF} 2220 2221 # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the 2222 # system and generate an INDEX-PRESENT file. 2223 fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1 2224 2225 # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which 2226 # correspond to lines in INDEX-PRESENT with hashes not appearing 2227 # in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in 2228 # INDEX-PRESENT has type - and there isn't a corresponding entry in 2229 # INDEX-OLD with type -. 2230 fetch_filter_unmodified_notpresent \ 2231 INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null 2232 2233 # For each entry in INDEX-PRESENT of type -, remove any corresponding 2234 # entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries 2235 # of type - from INDEX-PRESENT. 2236 fetch_filter_allowadd INDEX-PRESENT INDEX-NEW 2237 2238 # If ${ALLOWDELETE} != "yes", then remove any entries from 2239 # INDEX-PRESENT which don't correspond to entries in INDEX-NEW. 2240 fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW 2241 2242 # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in 2243 # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD, 2244 # replace the corresponding line of INDEX-NEW with one having the 2245 # same metadata as the entry in INDEX-PRESENT. 2246 fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW 2247 2248 # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical; 2249 # no need to update a file if it isn't changing. 2250 fetch_filter_uptodate INDEX-PRESENT INDEX-NEW 2251 2252 # Prepare to fetch files: Generate a list of the files we need, 2253 # copy the unmodified files we have into /files/, and generate 2254 # a list of patches to download. 2255 fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1 2256 2257 # Fetch files. 2258 fetch_files || return 1 2259 2260 # Create and populate install manifest directory; and report what 2261 # updates are available. 2262 fetch_create_manifest || return 1 2263 2264 # Warn about any upcoming EoL 2265 fetch_warn_eol || return 1 2266} 2267 2268# If StrictComponents is not "yes", generate a new components list 2269# with only the components which appear to be installed. 2270upgrade_guess_components () { 2271 if [ "${STRICTCOMPONENTS}" = "no" ]; then 2272 # Generate filtered INDEX-ALL with only the components listed 2273 # in COMPONENTS. 2274 fetch_filter_metadata_components $1 || return 1 2275 2276 # Tell the user why his disk is suddenly making lots of noise 2277 echo -n "Inspecting system... " 2278 2279 # Look at the files on disk, and assume that a component is 2280 # supposed to be present if it is more than half-present. 2281 cut -f 1-3 -d '|' < INDEX-ALL | 2282 tr '|' ' ' | 2283 while read C S F; do 2284 if [ -e ${BASEDIR}/${F} ]; then 2285 echo "+ ${C}|${S}" 2286 fi 2287 echo "= ${C}|${S}" 2288 done | 2289 sort | 2290 uniq -c | 2291 sed -E 's,^ +,,' > compfreq 2292 grep ' = ' compfreq | 2293 cut -f 1,3 -d ' ' | 2294 sort -k 2,2 -t ' ' > compfreq.total 2295 grep ' + ' compfreq | 2296 cut -f 1,3 -d ' ' | 2297 sort -k 2,2 -t ' ' > compfreq.present 2298 join -t ' ' -1 2 -2 2 compfreq.present compfreq.total | 2299 while read S P T; do 2300 if [ ${T} -ne 0 -a ${P} -gt `expr ${T} / 2` ]; then 2301 echo ${S} 2302 fi 2303 done > comp.present 2304 cut -f 2 -d ' ' < compfreq.total > comp.total 2305 rm INDEX-ALL compfreq compfreq.total compfreq.present 2306 2307 # We're done making noise. 2308 echo "done." 2309 2310 # Sometimes the kernel isn't installed where INDEX-ALL 2311 # thinks that it should be: In particular, it is often in 2312 # /boot/kernel instead of /boot/GENERIC or /boot/SMP. To 2313 # deal with this, if "kernel|X" is listed in comp.total 2314 # (i.e., is a component which would be upgraded if it is 2315 # found to be present) we will add it to comp.present. 2316 # If "kernel|<anything>" is in comp.total but "kernel|X" is 2317 # not, we print a warning -- the user is running a kernel 2318 # which isn't part of the release. 2319 KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'` 2320 grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present 2321 2322 if grep -qE "^kernel\|" comp.total && 2323 ! grep -qE "^kernel\|${KCOMP}\$" comp.total; then 2324 cat <<-EOF 2325 2326WARNING: This system is running a "${KCOMP}" kernel, which is not a 2327kernel configuration distributed as part of FreeBSD ${RELNUM}. 2328This kernel will not be updated: you MUST update the kernel manually 2329before running '`basename $0` [options] install'. 2330 EOF 2331 fi 2332 2333 # Re-sort the list of installed components and generate 2334 # the list of non-installed components. 2335 sort -u < comp.present > comp.present.tmp 2336 mv comp.present.tmp comp.present 2337 comm -13 comp.present comp.total > comp.absent 2338 2339 # Ask the user to confirm that what we have is correct. To 2340 # reduce user confusion, translate "X|Y" back to "X/Y" (as 2341 # subcomponents must be listed in the configuration file). 2342 echo 2343 echo -n "The following components of FreeBSD " 2344 echo "seem to be installed:" 2345 tr '|' '/' < comp.present | 2346 fmt -72 2347 echo 2348 echo -n "The following components of FreeBSD " 2349 echo "do not seem to be installed:" 2350 tr '|' '/' < comp.absent | 2351 fmt -72 2352 echo 2353 continuep || return 1 2354 echo 2355 2356 # Suck the generated list of components into ${COMPONENTS}. 2357 # Note that comp.present.tmp is used due to issues with 2358 # pipelines and setting variables. 2359 COMPONENTS="" 2360 tr '|' '/' < comp.present > comp.present.tmp 2361 while read C; do 2362 COMPONENTS="${COMPONENTS} ${C}" 2363 done < comp.present.tmp 2364 2365 # Delete temporary files 2366 rm comp.present comp.present.tmp comp.absent comp.total 2367 fi 2368} 2369 2370# If StrictComponents is not "yes", COMPONENTS contains an entry 2371# corresponding to the currently running kernel, and said kernel 2372# does not exist in the new release, add "kernel/generic" to the 2373# list of components. 2374upgrade_guess_new_kernel () { 2375 if [ "${STRICTCOMPONENTS}" = "no" ]; then 2376 # Grab the unfiltered metadata file. 2377 METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'` 2378 gunzip -c < files/${METAHASH}.gz > $1.all 2379 2380 # If "kernel/${KCOMP}" is in ${COMPONENTS} and that component 2381 # isn't in $1.all, we need to add kernel/generic. 2382 for C in ${COMPONENTS}; do 2383 if [ ${C} = "kernel/${KCOMP}" ] && 2384 ! grep -qE "^kernel\|${KCOMP}\|" $1.all; then 2385 COMPONENTS="${COMPONENTS} kernel/generic" 2386 NKERNCONF="GENERIC" 2387 cat <<-EOF 2388 2389WARNING: This system is running a "${KCOMP}" kernel, which is not a 2390kernel configuration distributed as part of FreeBSD ${RELNUM}. 2391As part of upgrading to FreeBSD ${RELNUM}, this kernel will be 2392replaced with a "generic" kernel. 2393 EOF 2394 continuep || return 1 2395 fi 2396 done 2397 2398 # Don't need this any more... 2399 rm $1.all 2400 fi 2401} 2402 2403# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into 2404# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades). 2405upgrade_oldall_to_oldnew () { 2406 # For each ${F}|... which appears in INDEX-ALL but does not appear 2407 # in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD. 2408 cut -f 1 -d '|' < $1 | 2409 sort -u > $1.paths 2410 cut -f 1 -d '|' < $2 | 2411 sort -u | 2412 comm -13 $1.paths - | 2413 lam - -s "|-||||||" | 2414 sort - $1 > $1.tmp 2415 mv $1.tmp $1 2416 2417 # Remove lines from INDEX-OLD which also appear in INDEX-ALL 2418 comm -23 $1 $2 > $1.tmp 2419 mv $1.tmp $1 2420 2421 # Remove lines from INDEX-ALL which have a file name not appearing 2422 # anywhere in INDEX-OLD (since these must be files which haven't 2423 # changed -- if they were new, there would be an entry of type "-"). 2424 cut -f 1 -d '|' < $1 | 2425 sort -u > $1.paths 2426 sort -k 1,1 -t '|' < $2 | 2427 join -t '|' - $1.paths | 2428 sort > $2.tmp 2429 rm $1.paths 2430 mv $2.tmp $2 2431 2432 # Rename INDEX-ALL to INDEX-NEW. 2433 mv $2 $3 2434} 2435 2436# Helper for upgrade_merge: Return zero true iff the two files differ only 2437# in the contents of their RCS tags. 2438samef () { 2439 X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}` 2440 Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}` 2441 2442 if [ $X = $Y ]; then 2443 return 0; 2444 else 2445 return 1; 2446 fi 2447} 2448 2449# From the list of "old" files in $1, merge changes in $2 with those in $3, 2450# and update $3 to reflect the hashes of merged files. 2451upgrade_merge () { 2452 # We only need to do anything if $1 is non-empty. 2453 if [ -s $1 ]; then 2454 cut -f 1 -d '|' $1 | 2455 sort > $1-paths 2456 2457 # Create staging area for merging files 2458 rm -rf merge/ 2459 while read F; do 2460 D=`dirname ${F}` 2461 mkdir -p merge/old/${D} 2462 mkdir -p merge/${OLDRELNUM}/${D} 2463 mkdir -p merge/${RELNUM}/${D} 2464 mkdir -p merge/new/${D} 2465 done < $1-paths 2466 2467 # Copy in files 2468 while read F; do 2469 # Currently installed file 2470 V=`look "${F}|" $2 | cut -f 7 -d '|'` 2471 gunzip < files/${V}.gz > merge/old/${F} 2472 2473 # Old release 2474 if look "${F}|" $1 | fgrep -q "|f|"; then 2475 V=`look "${F}|" $1 | cut -f 3 -d '|'` 2476 gunzip < files/${V}.gz \ 2477 > merge/${OLDRELNUM}/${F} 2478 fi 2479 2480 # New release 2481 if look "${F}|" $3 | cut -f 1,2,7 -d '|' | 2482 fgrep -q "|f|"; then 2483 V=`look "${F}|" $3 | cut -f 7 -d '|'` 2484 gunzip < files/${V}.gz \ 2485 > merge/${RELNUM}/${F} 2486 fi 2487 done < $1-paths 2488 2489 # Attempt to automatically merge changes 2490 echo -n "Attempting to automatically merge " 2491 echo -n "changes in files..." 2492 : > failed.merges 2493 while read F; do 2494 # If the file doesn't exist in the new release, 2495 # the result of "merging changes" is having the file 2496 # not exist. 2497 if ! [ -f merge/${RELNUM}/${F} ]; then 2498 continue 2499 fi 2500 2501 # If the file didn't exist in the old release, we're 2502 # going to throw away the existing file and hope that 2503 # the version from the new release is what we want. 2504 if ! [ -f merge/${OLDRELNUM}/${F} ]; then 2505 cp merge/${RELNUM}/${F} merge/new/${F} 2506 continue 2507 fi 2508 2509 # Some files need special treatment. 2510 case ${F} in 2511 /etc/spwd.db | /etc/pwd.db | /etc/login.conf.db) 2512 # Don't merge these -- we're rebuild them 2513 # after updates are installed. 2514 cp merge/old/${F} merge/new/${F} 2515 ;; 2516 *) 2517 if ! diff3 -E -m -L "current version" \ 2518 -L "${OLDRELNUM}" -L "${RELNUM}" \ 2519 merge/old/${F} \ 2520 merge/${OLDRELNUM}/${F} \ 2521 merge/${RELNUM}/${F} \ 2522 > merge/new/${F} 2>/dev/null; then 2523 echo ${F} >> failed.merges 2524 fi 2525 ;; 2526 esac 2527 done < $1-paths 2528 echo " done." 2529 2530 # Ask the user to handle any files which didn't merge. 2531 while read F; do 2532 # If the installed file differs from the version in 2533 # the old release only due to RCS tag expansion 2534 # then just use the version in the new release. 2535 if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then 2536 cp merge/${RELNUM}/${F} merge/new/${F} 2537 continue 2538 fi 2539 2540 cat <<-EOF 2541 2542The following file could not be merged automatically: ${F} 2543Press Enter to edit this file in ${EDITOR} and resolve the conflicts 2544manually... 2545 EOF 2546 while true; do 2547 read response </dev/tty 2548 if expr "${response}" : '[Aa][Cc][Cc][Ee][Pp][Tt]' > /dev/null; then 2549 echo 2550 break 2551 fi 2552 ${EDITOR} `pwd`/merge/new/${F} < /dev/tty 2553 2554 if ! grep -qE '^(<<<<<<<|=======|>>>>>>>)([[:space:]].*)?$' $(pwd)/merge/new/${F} ; then 2555 break 2556 fi 2557 cat <<-EOF 2558 2559Merge conflict markers remain in: ${F} 2560These must be resolved for the system to be functional. 2561 2562Press Enter to return to editing this file, or type "ACCEPT" to carry on with 2563these lines remaining in the file. 2564 EOF 2565 done 2566 done < failed.merges 2567 rm failed.merges 2568 2569 # Ask the user to confirm that he likes how the result 2570 # of merging files. 2571 while read F; do 2572 # Skip files which haven't changed except possibly 2573 # in their RCS tags. 2574 if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] && 2575 samef merge/old/${F} merge/new/${F}; then 2576 continue 2577 fi 2578 2579 # Skip files where the installed file differs from 2580 # the old file only due to RCS tags. 2581 if [ -f merge/old/${F} ] && 2582 [ -f merge/${OLDRELNUM}/${F} ] && 2583 samef merge/old/${F} merge/${OLDRELNUM}/${F}; then 2584 continue 2585 fi 2586 2587 # Warn about files which are ceasing to exist. 2588 if ! [ -f merge/new/${F} ]; then 2589 cat <<-EOF 2590 2591The following file will be removed, as it no longer exists in 2592FreeBSD ${RELNUM}: ${F} 2593 EOF 2594 continuep < /dev/tty || return 1 2595 continue 2596 fi 2597 2598 # Print changes for the user's approval. 2599 cat <<-EOF 2600 2601The following changes, which occurred between FreeBSD ${OLDRELNUM} and 2602FreeBSD ${RELNUM} have been merged into ${F}: 2603EOF 2604 diff -U 5 -L "current version" -L "new version" \ 2605 merge/old/${F} merge/new/${F} || true 2606 continuep < /dev/tty || return 1 2607 done < $1-paths 2608 2609 # Store merged files. 2610 while read F; do 2611 if [ -f merge/new/${F} ]; then 2612 V=`${SHA256} -q merge/new/${F}` 2613 2614 gzip -c < merge/new/${F} > files/${V}.gz 2615 echo "${F}|${V}" 2616 fi 2617 done < $1-paths > newhashes 2618 2619 # Pull lines out from $3 which need to be updated to 2620 # reflect merged files. 2621 while read F; do 2622 look "${F}|" $3 2623 done < $1-paths > $3-oldlines 2624 2625 # Update lines to reflect merged files 2626 join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8 \ 2627 $3-oldlines newhashes > $3-newlines 2628 2629 # Remove old lines from $3 and add new lines. 2630 sort $3-oldlines | 2631 comm -13 - $3 | 2632 sort - $3-newlines > $3.tmp 2633 mv $3.tmp $3 2634 2635 # Clean up 2636 rm $1-paths newhashes $3-oldlines $3-newlines 2637 rm -rf merge/ 2638 fi 2639 2640 # We're done with merging files. 2641 rm $1 2642} 2643 2644# Do the work involved in fetching upgrades to a new release 2645upgrade_run () { 2646 workdir_init || return 1 2647 2648 # Prepare the mirror list. 2649 fetch_pick_server_init && fetch_pick_server 2650 2651 # Try to fetch the public key until we run out of servers. 2652 while ! fetch_key; do 2653 fetch_pick_server || return 1 2654 done 2655 2656 # Try to fetch the metadata index signature ("tag") until we run 2657 # out of available servers; and sanity check the downloaded tag. 2658 while ! fetch_tag; do 2659 fetch_pick_server || return 1 2660 done 2661 fetch_tagsanity || return 1 2662 2663 # Fetch the INDEX-OLD and INDEX-ALL. 2664 fetch_metadata INDEX-OLD INDEX-ALL || return 1 2665 2666 # If StrictComponents is not "yes", generate a new components list 2667 # with only the components which appear to be installed. 2668 upgrade_guess_components INDEX-ALL || return 1 2669 2670 # Generate filtered INDEX-OLD and INDEX-ALL files containing only 2671 # the components we want and without anything marked as "Ignore". 2672 fetch_filter_metadata INDEX-OLD || return 1 2673 fetch_filter_metadata INDEX-ALL || return 1 2674 2675 # Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD. 2676 sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp 2677 mv INDEX-OLD.tmp INDEX-OLD 2678 rm INDEX-ALL 2679 2680 # Adjust variables for fetching files from the new release. 2681 OLDRELNUM=${RELNUM} 2682 RELNUM=${TARGETRELEASE} 2683 OLDFETCHDIR=${FETCHDIR} 2684 FETCHDIR=${RELNUM}/${ARCH} 2685 2686 # Try to fetch the NEW metadata index signature ("tag") until we run 2687 # out of available servers; and sanity check the downloaded tag. 2688 while ! fetch_tag; do 2689 fetch_pick_server || return 1 2690 done 2691 2692 # Fetch the new INDEX-ALL. 2693 fetch_metadata INDEX-ALL || return 1 2694 2695 # If StrictComponents is not "yes", COMPONENTS contains an entry 2696 # corresponding to the currently running kernel, and said kernel 2697 # does not exist in the new release, add "kernel/generic" to the 2698 # list of components. 2699 upgrade_guess_new_kernel INDEX-ALL || return 1 2700 2701 # Filter INDEX-ALL to contain only the components we want and without 2702 # anything marked as "Ignore". 2703 fetch_filter_metadata INDEX-ALL || return 1 2704 2705 # Convert INDEX-OLD (last release) and INDEX-ALL (new release) into 2706 # INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades). 2707 upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW 2708 2709 # Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR} 2710 fetch_filter_kernel_names INDEX-NEW ${NKERNCONF} 2711 fetch_filter_kernel_names INDEX-OLD ${KERNCONF} 2712 2713 # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the 2714 # system and generate an INDEX-PRESENT file. 2715 fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1 2716 2717 # Based on ${MERGECHANGES}, generate a file tomerge-old with the 2718 # paths and hashes of old versions of files to merge. 2719 fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old 2720 2721 # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which 2722 # correspond to lines in INDEX-PRESENT with hashes not appearing 2723 # in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in 2724 # INDEX-PRESENT has type - and there isn't a corresponding entry in 2725 # INDEX-OLD with type -. 2726 fetch_filter_unmodified_notpresent \ 2727 INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old 2728 2729 # For each entry in INDEX-PRESENT of type -, remove any corresponding 2730 # entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries 2731 # of type - from INDEX-PRESENT. 2732 fetch_filter_allowadd INDEX-PRESENT INDEX-NEW 2733 2734 # If ${ALLOWDELETE} != "yes", then remove any entries from 2735 # INDEX-PRESENT which don't correspond to entries in INDEX-NEW. 2736 fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW 2737 2738 # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in 2739 # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD, 2740 # replace the corresponding line of INDEX-NEW with one having the 2741 # same metadata as the entry in INDEX-PRESENT. 2742 fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW 2743 2744 # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical; 2745 # no need to update a file if it isn't changing. 2746 fetch_filter_uptodate INDEX-PRESENT INDEX-NEW 2747 2748 # Fetch "clean" files from the old release for merging changes. 2749 fetch_files_premerge tomerge-old 2750 2751 # Prepare to fetch files: Generate a list of the files we need, 2752 # copy the unmodified files we have into /files/, and generate 2753 # a list of patches to download. 2754 fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1 2755 2756 # Fetch patches from to-${RELNUM}/${ARCH}/bp/ 2757 PATCHDIR=to-${RELNUM}/${ARCH}/bp 2758 fetch_files || return 1 2759 2760 # Merge configuration file changes. 2761 upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1 2762 2763 # Create and populate install manifest directory; and report what 2764 # updates are available. 2765 fetch_create_manifest || return 1 2766 2767 # Leave a note behind to tell the "install" command that the kernel 2768 # needs to be installed before the world. 2769 touch ${BDHASH}-install/kernelfirst 2770 2771 # Remind the user that they need to run "freebsd-update install" 2772 # to install the downloaded bits, in case they didn't RTFM. 2773 echo "To install the downloaded upgrades, run '`basename $0` [options] install'." 2774} 2775 2776# Make sure that all the file hashes mentioned in $@ have corresponding 2777# gzipped files stored in /files/. 2778install_verify () { 2779 # Generate a list of hashes 2780 cat $@ | 2781 cut -f 2,7 -d '|' | 2782 grep -E '^f' | 2783 cut -f 2 -d '|' | 2784 sort -u > filelist 2785 2786 # Make sure all the hashes exist 2787 while read HASH; do 2788 if ! [ -f files/${HASH}.gz ]; then 2789 echo -n "Update files missing -- " 2790 echo "this should never happen." 2791 echo "Re-run '`basename $0` [options] fetch'." 2792 return 1 2793 fi 2794 done < filelist 2795 2796 # Clean up 2797 rm filelist 2798} 2799 2800# Remove the system immutable flag from files 2801install_unschg () { 2802 # Generate file list 2803 cat $@ | 2804 cut -f 1 -d '|' > filelist 2805 2806 # Remove flags 2807 while read F; do 2808 if ! [ -e ${BASEDIR}/${F} ]; then 2809 continue 2810 else 2811 echo ${BASEDIR}/${F} 2812 fi 2813 done < filelist | xargs chflags noschg || return 1 2814 2815 # Clean up 2816 rm filelist 2817} 2818 2819# Decide which directory name to use for kernel backups. 2820backup_kernel_finddir () { 2821 CNT=0 2822 while true ; do 2823 # Pathname does not exist, so it is OK use that name 2824 # for backup directory. 2825 if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then 2826 return 0 2827 fi 2828 2829 # If directory do exist, we only use if it has our 2830 # marker file. 2831 if [ -d $BASEDIR/$BACKUPKERNELDIR -a \ 2832 -e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then 2833 return 0 2834 fi 2835 2836 # We could not use current directory name, so add counter to 2837 # the end and try again. 2838 CNT=$((CNT + 1)) 2839 if [ $CNT -gt 9 ]; then 2840 echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)" 2841 exit 1 2842 fi 2843 BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`" 2844 BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}" 2845 done 2846} 2847 2848# Backup the current kernel using hardlinks, if not disabled by user. 2849# Since we delete all files in the directory used for previous backups 2850# we create a marker file called ".freebsd-update" in the directory so 2851# we can determine on the next run that the directory was created by 2852# freebsd-update and we then do not accidentally remove user files in 2853# the unlikely case that the user has created a directory with a 2854# conflicting name. 2855backup_kernel () { 2856 # Only make kernel backup is so configured. 2857 if [ $BACKUPKERNEL != yes ]; then 2858 return 0 2859 fi 2860 2861 # Decide which directory name to use for kernel backups. 2862 backup_kernel_finddir 2863 2864 # Remove old kernel backup files. If $BACKUPKERNELDIR was 2865 # "not ours", backup_kernel_finddir would have exited, so 2866 # deleting the directory content is as safe as we can make it. 2867 if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then 2868 rm -fr $BASEDIR/$BACKUPKERNELDIR 2869 fi 2870 2871 # Create directories for backup. 2872 mkdir -p $BASEDIR/$BACKUPKERNELDIR 2873 mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \ 2874 mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null 2875 2876 # Mark the directory as having been created by freebsd-update. 2877 touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update 2878 if [ $? -ne 0 ]; then 2879 echo "Could not create kernel backup directory" 2880 exit 1 2881 fi 2882 2883 # Disable pathname expansion to be sure *.symbols is not 2884 # expanded. 2885 set -f 2886 2887 # Use find to ignore symbol files, unless disabled by user. 2888 if [ $BACKUPKERNELSYMBOLFILES = yes ]; then 2889 FINDFILTER="" 2890 else 2891 FINDFILTER="-a ! -name *.debug -a ! -name *.symbols" 2892 fi 2893 2894 # Backup all the kernel files using hardlinks. 2895 (cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \ 2896 cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;) 2897 2898 # Re-enable patchname expansion. 2899 set +f 2900} 2901 2902# Check for and remove an existing directory that conflicts with the file or 2903# symlink that we are going to install. 2904dir_conflict () { 2905 if [ -d "$1" ]; then 2906 echo "Removing conflicting directory $1" 2907 rm -rf -- "$1" 2908 fi 2909} 2910 2911# Install new files 2912install_from_index () { 2913 # First pass: Do everything apart from setting file flags. We 2914 # can't set flags yet, because schg inhibits hard linking. 2915 sort -k 1,1 -t '|' $1 | 2916 tr '|' ' ' | 2917 while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do 2918 case ${TYPE} in 2919 d) 2920 # Create a directory. A file may change to a directory 2921 # on upgrade (PR273661). If that happens, remove the 2922 # file first. 2923 if [ -e "${BASEDIR}/${FPATH}" ] && \ 2924 ! [ -d "${BASEDIR}/${FPATH}" ]; then 2925 rm -f -- "${BASEDIR}/${FPATH}" 2926 fi 2927 install -d -o ${OWNER} -g ${GROUP} \ 2928 -m ${PERM} ${BASEDIR}/${FPATH} 2929 ;; 2930 f) 2931 dir_conflict "${BASEDIR}/${FPATH}" 2932 if [ -z "${LINK}" ]; then 2933 # Create a file, without setting flags. 2934 gunzip < files/${HASH}.gz > ${HASH} 2935 install -S -o ${OWNER} -g ${GROUP} \ 2936 -m ${PERM} ${HASH} ${BASEDIR}/${FPATH} 2937 rm ${HASH} 2938 else 2939 # Create a hard link. 2940 ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH} 2941 fi 2942 ;; 2943 L) 2944 dir_conflict "${BASEDIR}/${FPATH}" 2945 # Create a symlink 2946 ln -sfh ${HASH} ${BASEDIR}/${FPATH} 2947 ;; 2948 esac 2949 done 2950 2951 # Perform a second pass, adding file flags. 2952 tr '|' ' ' < $1 | 2953 while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do 2954 if [ ${TYPE} = "f" ] && 2955 ! [ ${FLAGS} = "0" ]; then 2956 chflags ${FLAGS} ${BASEDIR}/${FPATH} 2957 fi 2958 done 2959} 2960 2961# Remove files which we want to delete 2962install_delete () { 2963 # Generate list of new files 2964 cut -f 1 -d '|' < $2 | 2965 sort > newfiles 2966 2967 # Generate subindex of old files we want to nuke 2968 sort -k 1,1 -t '|' $1 | 2969 join -t '|' -v 1 - newfiles | 2970 sort -r -k 1,1 -t '|' | 2971 cut -f 1,2 -d '|' | 2972 tr '|' ' ' > killfiles 2973 2974 # Remove the offending bits 2975 while read FPATH TYPE; do 2976 case ${TYPE} in 2977 d) 2978 rmdir ${BASEDIR}/${FPATH} 2979 ;; 2980 f) 2981 if [ -f "${BASEDIR}/${FPATH}" ]; then 2982 rm "${BASEDIR}/${FPATH}" 2983 fi 2984 ;; 2985 L) 2986 if [ -L "${BASEDIR}/${FPATH}" ]; then 2987 rm "${BASEDIR}/${FPATH}" 2988 fi 2989 ;; 2990 esac 2991 done < killfiles 2992 2993 # Clean up 2994 rm newfiles killfiles 2995} 2996 2997# Install new files, delete old files, and update generated files 2998install_files () { 2999 # If we haven't already dealt with the kernel, deal with it. 3000 if ! [ -f $1/kerneldone ]; then 3001 grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD 3002 grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW 3003 3004 # Backup current kernel before installing a new one 3005 backup_kernel || return 1 3006 3007 # Install new files 3008 install_from_index INDEX-NEW || return 1 3009 3010 # Remove files which need to be deleted 3011 install_delete INDEX-OLD INDEX-NEW || return 1 3012 3013 # Update linker.hints if necessary 3014 if [ -s INDEX-OLD -o -s INDEX-NEW ]; then 3015 kldxref -R ${BASEDIR}/boot/ 2>/dev/null 3016 fi 3017 3018 # We've finished updating the kernel. 3019 touch $1/kerneldone 3020 3021 # Do we need to ask for a reboot now? 3022 if [ -f $1/kernelfirst ] && 3023 [ -s INDEX-OLD -o -s INDEX-NEW ]; then 3024 cat <<-EOF 3025 3026Kernel updates have been installed. Please reboot and run 3027'`basename $0` [options] install' again to finish installing updates. 3028 EOF 3029 exit 0 3030 fi 3031 fi 3032 3033 # If we haven't already dealt with the world, deal with it. 3034 if ! [ -f $1/worlddone ]; then 3035 # Create any necessary directories first 3036 grep -vE '^/boot/' $1/INDEX-NEW | 3037 grep -E '^[^|]+\|d\|' > INDEX-NEW 3038 install_from_index INDEX-NEW || return 1 3039 3040 # Install new runtime linker 3041 grep -vE '^/boot/' $1/INDEX-NEW | 3042 grep -vE '^[^|]+\|d\|' | 3043 grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW 3044 install_from_index INDEX-NEW || return 1 3045 3046 # Install new shared libraries next 3047 grep -vE '^/boot/' $1/INDEX-NEW | 3048 grep -vE '^[^|]+\|d\|' | 3049 grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | 3050 grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW 3051 install_from_index INDEX-NEW || return 1 3052 3053 # Deal with everything else 3054 grep -vE '^/boot/' $1/INDEX-OLD | 3055 grep -vE '^[^|]+\|d\|' | 3056 grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | 3057 grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD 3058 grep -vE '^/boot/' $1/INDEX-NEW | 3059 grep -vE '^[^|]+\|d\|' | 3060 grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | 3061 grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW 3062 install_from_index INDEX-NEW || return 1 3063 install_delete INDEX-OLD INDEX-NEW || return 1 3064 3065 # Restart host sshd if running (PR263489). Note that this does 3066 # not affect child sshd processes handling existing sessions. 3067 if [ "$BASEDIR" = / ] && \ 3068 service sshd status >/dev/null 2>/dev/null; then 3069 echo 3070 echo "Restarting sshd after upgrade" 3071 service sshd restart 3072 fi 3073 3074 # Rehash certs if we actually have certctl installed. 3075 if which certctl>/dev/null; then 3076 env DESTDIR=${BASEDIR} certctl rehash 3077 fi 3078 3079 # Rebuild generated pwd files and /etc/login.conf.db. 3080 pwd_mkdb -d ${BASEDIR}/etc -p ${BASEDIR}/etc/master.passwd 3081 cap_mkdb ${BASEDIR}/etc/login.conf 3082 3083 # Rebuild man page databases, if necessary. 3084 for D in /usr/share/man /usr/share/openssl/man; do 3085 if [ ! -d ${BASEDIR}/$D ]; then 3086 continue 3087 fi 3088 if [ -f ${BASEDIR}/$D/mandoc.db ] && \ 3089 [ -z "$(find ${BASEDIR}/$D -type f -newer ${BASEDIR}/$D/mandoc.db)" ]; then 3090 continue; 3091 fi 3092 makewhatis ${BASEDIR}/$D 3093 done 3094 3095 # We've finished installing the world and deleting old files 3096 # which are not shared libraries. 3097 touch $1/worlddone 3098 3099 # Do we need to ask the user to portupgrade now? 3100 grep -vE '^/boot/' $1/INDEX-NEW | 3101 grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' | 3102 cut -f 1 -d '|' | 3103 sort > newfiles 3104 if grep -vE '^/boot/' $1/INDEX-OLD | 3105 grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' | 3106 cut -f 1 -d '|' | 3107 sort | 3108 join -v 1 - newfiles | 3109 grep -q .; then 3110 cat <<-EOF 3111 3112Completing this upgrade requires removing old shared object files. 3113Please rebuild all installed 3rd party software (e.g., programs 3114installed from the ports tree) and then run 3115'`basename $0` [options] install' again to finish installing updates. 3116 EOF 3117 rm newfiles 3118 exit 0 3119 fi 3120 rm newfiles 3121 fi 3122 3123 # Remove old shared libraries 3124 grep -vE '^/boot/' $1/INDEX-NEW | 3125 grep -vE '^[^|]+\|d\|' | 3126 grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW 3127 grep -vE '^/boot/' $1/INDEX-OLD | 3128 grep -vE '^[^|]+\|d\|' | 3129 grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD 3130 install_delete INDEX-OLD INDEX-NEW || return 1 3131 3132 # Remove old directories 3133 grep -vE '^/boot/' $1/INDEX-NEW | 3134 grep -E '^[^|]+\|d\|' > INDEX-NEW 3135 grep -vE '^/boot/' $1/INDEX-OLD | 3136 grep -E '^[^|]+\|d\|' > INDEX-OLD 3137 install_delete INDEX-OLD INDEX-NEW || return 1 3138 3139 # Remove temporary files 3140 rm INDEX-OLD INDEX-NEW 3141} 3142 3143# Rearrange bits to allow the installed updates to be rolled back 3144install_setup_rollback () { 3145 # Remove the "reboot after installing kernel", "kernel updated", and 3146 # "finished installing the world" flags if present -- they are 3147 # irrelevant when rolling back updates. 3148 if [ -f ${BDHASH}-install/kernelfirst ]; then 3149 rm ${BDHASH}-install/kernelfirst 3150 rm ${BDHASH}-install/kerneldone 3151 fi 3152 if [ -f ${BDHASH}-install/worlddone ]; then 3153 rm ${BDHASH}-install/worlddone 3154 fi 3155 3156 if [ -L ${BDHASH}-rollback ]; then 3157 mv ${BDHASH}-rollback ${BDHASH}-install/rollback 3158 fi 3159 3160 mv ${BDHASH}-install ${BDHASH}-rollback 3161} 3162 3163# Actually install updates 3164install_run () { 3165 echo -n "Installing updates..." 3166 3167 # Make sure we have all the files we should have 3168 install_verify ${BDHASH}-install/INDEX-OLD \ 3169 ${BDHASH}-install/INDEX-NEW || return 1 3170 3171 # Remove system immutable flag from files 3172 install_unschg ${BDHASH}-install/INDEX-OLD \ 3173 ${BDHASH}-install/INDEX-NEW || return 1 3174 3175 # Install new files, delete old files, and update linker.hints 3176 install_files ${BDHASH}-install || return 1 3177 3178 # Rearrange bits to allow the installed updates to be rolled back 3179 install_setup_rollback 3180 3181 echo " done." 3182} 3183 3184# Rearrange bits to allow the previous set of updates to be rolled back next. 3185rollback_setup_rollback () { 3186 if [ -L ${BDHASH}-rollback/rollback ]; then 3187 mv ${BDHASH}-rollback/rollback rollback-tmp 3188 rm -r ${BDHASH}-rollback/ 3189 rm ${BDHASH}-rollback 3190 mv rollback-tmp ${BDHASH}-rollback 3191 else 3192 rm -r ${BDHASH}-rollback/ 3193 rm ${BDHASH}-rollback 3194 fi 3195} 3196 3197# Install old files, delete new files, and update linker.hints 3198rollback_files () { 3199 # Create directories first. They may be needed by files we will 3200 # install in subsequent steps (PR273950). 3201 awk -F \| '{if ($2 == "d") print }' $1/INDEX-OLD > INDEX-OLD 3202 install_from_index INDEX-OLD || return 1 3203 3204 # Install old shared library files which don't have the same path as 3205 # a new shared library file. 3206 grep -vE '^/boot/' $1/INDEX-NEW | 3207 grep -E '/lib/.*\.so\.[0-9]+\|' | 3208 cut -f 1 -d '|' | 3209 sort > INDEX-NEW.libs.flist 3210 grep -vE '^/boot/' $1/INDEX-OLD | 3211 grep -E '/lib/.*\.so\.[0-9]+\|' | 3212 sort -k 1,1 -t '|' - | 3213 join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD 3214 install_from_index INDEX-OLD || return 1 3215 3216 # Deal with files which are neither kernel nor shared library 3217 grep -vE '^/boot/' $1/INDEX-OLD | 3218 grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD 3219 grep -vE '^/boot/' $1/INDEX-NEW | 3220 grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW 3221 install_from_index INDEX-OLD || return 1 3222 install_delete INDEX-NEW INDEX-OLD || return 1 3223 3224 # Install any old shared library files which we didn't install above. 3225 grep -vE '^/boot/' $1/INDEX-OLD | 3226 grep -E '/lib/.*\.so\.[0-9]+\|' | 3227 sort -k 1,1 -t '|' - | 3228 join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD 3229 install_from_index INDEX-OLD || return 1 3230 3231 # Delete unneeded shared library files 3232 grep -vE '^/boot/' $1/INDEX-OLD | 3233 grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD 3234 grep -vE '^/boot/' $1/INDEX-NEW | 3235 grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW 3236 install_delete INDEX-NEW INDEX-OLD || return 1 3237 3238 # Deal with kernel files 3239 grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD 3240 grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW 3241 install_from_index INDEX-OLD || return 1 3242 install_delete INDEX-NEW INDEX-OLD || return 1 3243 if [ -s INDEX-OLD -o -s INDEX-NEW ]; then 3244 kldxref -R /boot/ 2>/dev/null 3245 fi 3246 3247 # Remove temporary files 3248 rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist 3249} 3250 3251# Actually rollback updates 3252rollback_run () { 3253 echo -n "Uninstalling updates..." 3254 3255 # If there are updates waiting to be installed, remove them; we 3256 # want the user to re-run 'fetch' after rolling back updates. 3257 if [ -L ${BDHASH}-install ]; then 3258 rm -r ${BDHASH}-install/ 3259 rm ${BDHASH}-install 3260 fi 3261 3262 # Make sure we have all the files we should have 3263 install_verify ${BDHASH}-rollback/INDEX-NEW \ 3264 ${BDHASH}-rollback/INDEX-OLD || return 1 3265 3266 # Remove system immutable flag from files 3267 install_unschg ${BDHASH}-rollback/INDEX-NEW \ 3268 ${BDHASH}-rollback/INDEX-OLD || return 1 3269 3270 # Install old files, delete new files, and update linker.hints 3271 rollback_files ${BDHASH}-rollback || return 1 3272 3273 # Remove the rollback directory and the symlink pointing to it; and 3274 # rearrange bits to allow the previous set of updates to be rolled 3275 # back next. 3276 rollback_setup_rollback 3277 3278 echo " done." 3279} 3280 3281# Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences. 3282IDS_compare () { 3283 # Get all the lines which mismatch in something other than file 3284 # flags. We ignore file flags because sysinstall doesn't seem to 3285 # set them when it installs FreeBSD; warning about these adds a 3286 # very large amount of noise. 3287 cut -f 1-5,7-8 -d '|' $1 > $1.noflags 3288 sort -k 1,1 -t '|' $1.noflags > $1.sorted 3289 cut -f 1-5,7-8 -d '|' $2 | 3290 comm -13 $1.noflags - | 3291 fgrep -v '|-|||||' | 3292 sort -k 1,1 -t '|' | 3293 join -t '|' $1.sorted - > INDEX-NOTMATCHING 3294 3295 # Ignore files which match IDSIGNOREPATHS. 3296 for X in ${IDSIGNOREPATHS}; do 3297 grep -E "^${X}" INDEX-NOTMATCHING 3298 done | 3299 sort -u | 3300 comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp 3301 mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING 3302 3303 # Go through the lines and print warnings. 3304 local IFS='|' 3305 while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do 3306 # Warn about different object types. 3307 if ! [ "${TYPE}" = "${P_TYPE}" ]; then 3308 echo -n "${FPATH} is a " 3309 case "${P_TYPE}" in 3310 f) echo -n "regular file, " 3311 ;; 3312 d) echo -n "directory, " 3313 ;; 3314 L) echo -n "symlink, " 3315 ;; 3316 esac 3317 echo -n "but should be a " 3318 case "${TYPE}" in 3319 f) echo -n "regular file." 3320 ;; 3321 d) echo -n "directory." 3322 ;; 3323 L) echo -n "symlink." 3324 ;; 3325 esac 3326 echo 3327 3328 # Skip other tests, since they don't make sense if 3329 # we're comparing different object types. 3330 continue 3331 fi 3332 3333 # Warn about different owners. 3334 if ! [ "${OWNER}" = "${P_OWNER}" ]; then 3335 echo -n "${FPATH} is owned by user id ${P_OWNER}, " 3336 echo "but should be owned by user id ${OWNER}." 3337 fi 3338 3339 # Warn about different groups. 3340 if ! [ "${GROUP}" = "${P_GROUP}" ]; then 3341 echo -n "${FPATH} is owned by group id ${P_GROUP}, " 3342 echo "but should be owned by group id ${GROUP}." 3343 fi 3344 3345 # Warn about different permissions. We do not warn about 3346 # different permissions on symlinks, since some archivers 3347 # don't extract symlink permissions correctly and they are 3348 # ignored anyway. 3349 if ! [ "${PERM}" = "${P_PERM}" ] && 3350 ! [ "${TYPE}" = "L" ]; then 3351 echo -n "${FPATH} has ${P_PERM} permissions, " 3352 echo "but should have ${PERM} permissions." 3353 fi 3354 3355 # Warn about different file hashes / symlink destinations. 3356 if ! [ "${HASH}" = "${P_HASH}" ]; then 3357 if [ "${TYPE}" = "L" ]; then 3358 echo -n "${FPATH} is a symlink to ${P_HASH}, " 3359 echo "but should be a symlink to ${HASH}." 3360 fi 3361 if [ "${TYPE}" = "f" ]; then 3362 echo -n "${FPATH} has SHA256 hash ${P_HASH}, " 3363 echo "but should have SHA256 hash ${HASH}." 3364 fi 3365 fi 3366 3367 # We don't warn about different hard links, since some 3368 # some archivers break hard links, and as long as the 3369 # underlying data is correct they really don't matter. 3370 done < INDEX-NOTMATCHING 3371 3372 # Clean up 3373 rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING 3374} 3375 3376# Do the work involved in comparing the system to a "known good" index 3377IDS_run () { 3378 workdir_init || return 1 3379 3380 # Prepare the mirror list. 3381 fetch_pick_server_init && fetch_pick_server 3382 3383 # Try to fetch the public key until we run out of servers. 3384 while ! fetch_key; do 3385 fetch_pick_server || return 1 3386 done 3387 3388 # Try to fetch the metadata index signature ("tag") until we run 3389 # out of available servers; and sanity check the downloaded tag. 3390 while ! fetch_tag; do 3391 fetch_pick_server || return 1 3392 done 3393 fetch_tagsanity || return 1 3394 3395 # Fetch INDEX-OLD and INDEX-ALL. 3396 fetch_metadata INDEX-OLD INDEX-ALL || return 1 3397 3398 # Generate filtered INDEX-OLD and INDEX-ALL files containing only 3399 # the components we want and without anything marked as "Ignore". 3400 fetch_filter_metadata INDEX-OLD || return 1 3401 fetch_filter_metadata INDEX-ALL || return 1 3402 3403 # Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL. 3404 sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp 3405 mv INDEX-ALL.tmp INDEX-ALL 3406 rm INDEX-OLD 3407 3408 # Translate /boot/${KERNCONF} to ${KERNELDIR} 3409 fetch_filter_kernel_names INDEX-ALL ${KERNCONF} 3410 3411 # Inspect the system and generate an INDEX-PRESENT file. 3412 fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1 3413 3414 # Compare INDEX-ALL and INDEX-PRESENT and print warnings about any 3415 # differences. 3416 IDS_compare INDEX-ALL INDEX-PRESENT 3417} 3418 3419#### Main functions -- call parameter-handling and core functions 3420 3421# Using the command line, configuration file, and defaults, 3422# set all the parameters which are needed later. 3423get_params () { 3424 init_params 3425 parse_cmdline $@ 3426 parse_conffile 3427 default_params 3428} 3429 3430# Fetch command. Make sure that we're being called 3431# interactively, then run fetch_check_params and fetch_run 3432cmd_fetch () { 3433 finalize_components_config ${COMPONENTS} 3434 if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then 3435 echo -n "`basename $0` fetch should not " 3436 echo "be run non-interactively." 3437 echo "Run `basename $0` cron instead." 3438 exit 1 3439 fi 3440 fetch_check_params 3441 fetch_run || exit 1 3442 ISFETCHED=1 3443} 3444 3445# Cron command. Make sure the parameters are sensible; wait 3446# rand(3600) seconds; then fetch updates. While fetching updates, 3447# send output to a temporary file; only print that file if the 3448# fetching failed. 3449cmd_cron () { 3450 fetch_check_params 3451 sleep `jot -r 1 0 3600` 3452 3453 TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1 3454 finalize_components_config ${COMPONENTS} >> ${TMPFILE} 3455 if ! fetch_run >> ${TMPFILE} || 3456 ! grep -q "No updates needed" ${TMPFILE} || 3457 [ ${VERBOSELEVEL} = "debug" ]; then 3458 mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE} 3459 fi 3460 ISFETCHED=1 3461 3462 rm ${TMPFILE} 3463} 3464 3465# Fetch files for upgrading to a new release. 3466cmd_upgrade () { 3467 finalize_components_config ${COMPONENTS} 3468 upgrade_check_params 3469 upgrade_run || exit 1 3470} 3471 3472# Check if there are fetched updates ready to install. 3473# Chdir into the working directory. 3474cmd_updatesready () { 3475 finalize_components_config ${COMPONENTS} 3476 # Check if working directory exists (if not, no updates pending) 3477 if ! [ -e "${WORKDIR}" ]; then 3478 echo "No updates are available to install." 3479 exit 2 3480 fi 3481 3482 # Change into working directory (fail if no permission/directory etc.) 3483 cd ${WORKDIR} || exit 1 3484 3485 # Construct a unique name from ${BASEDIR} 3486 BDHASH=`echo ${BASEDIR} | sha256 -q` 3487 3488 # Check that we have updates ready to install 3489 if ! [ -L ${BDHASH}-install ]; then 3490 echo "No updates are available to install." 3491 exit 2 3492 fi 3493 3494 echo "There are updates available to install." 3495 echo "Run '`basename $0` [options] install' to proceed." 3496} 3497 3498# Install downloaded updates. 3499cmd_install () { 3500 finalize_components_config ${COMPONENTS} 3501 install_check_params 3502 install_create_be 3503 install_run || exit 1 3504} 3505 3506# Rollback most recently installed updates. 3507cmd_rollback () { 3508 finalize_components_config ${COMPONENTS} 3509 rollback_check_params 3510 rollback_run || exit 1 3511} 3512 3513# Compare system against a "known good" index. 3514cmd_IDS () { 3515 finalize_components_config ${COMPONENTS} 3516 IDS_check_params 3517 IDS_run || exit 1 3518} 3519 3520# Output configuration. 3521cmd_showconfig () { 3522 finalize_components_config ${COMPONENTS} 3523 for X in ${CONFIGOPTIONS}; do 3524 echo $X=$(eval echo \$${X}) 3525 done 3526} 3527 3528#### Entry point 3529 3530# Make sure we find utilities from the base system 3531export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH} 3532 3533# Set a pager if the user doesn't 3534if [ -z "$PAGER" ]; then 3535 PAGER=/usr/bin/less 3536fi 3537 3538# Set LC_ALL in order to avoid problems with character ranges like [A-Z]. 3539export LC_ALL=C 3540 3541# Clear environment variables that may affect operation of tools that we use. 3542unset GREP_OPTIONS 3543 3544get_params $@ 3545for COMMAND in ${COMMANDS}; do 3546 cmd_${COMMAND} 3547done 3548