When performing an "upgrade" (moving between FreeBSD releases, as opposed to "update" which merely applies security/errata updates to the installed release) FreeBSD Update: 1. Generates a list of "files needing to be merged", namely those files which don't match the version installed in the "old" release and have paths matching the MergeChanges configuration directive (by default, /boot/device.hints and everything under /etc/). and later on, 2. Compares the currently-installed files to the versions in the "new" release, removing index entries for files which "don't need to be updated because they're not changing". Unfortunately if a file falls into both of these categories -- that is to say, if a file in /etc/ is the same as the version in the new release and not the same as the version in the old release -- the resulting "merge" step saw that the file was no longer listed as being part of the new release, resulting in the file being deleted. For the first 18 years of FreeBSD Update's existence, this never happened, since $FreeBSD$ tags resulted in "new release" files always being different from any files systems would already have installed. This commit fixes this behaviour by only placing a file into the "files needing to be merged" list if it does not match the version in the old release *or* the version in the new release. Reported by: des Reviewed by: delphij (earlier version), des, emaste MFC after: 7 days X-EN-Candidate: yes Differential Revision: https://reviews.freebsd.org/D39973
3517 lines
94 KiB
Bash
3517 lines
94 KiB
Bash
#!/bin/sh
|
|
|
|
#-
|
|
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
#
|
|
# Copyright 2004-2007 Colin Percival
|
|
# All rights reserved
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted providing that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
# $FreeBSD$
|
|
|
|
#### Usage function -- called from command-line handling code.
|
|
|
|
# Usage instructions. Options not listed:
|
|
# --debug -- don't filter output from utilities
|
|
# --no-stats -- don't show progress statistics while fetching files
|
|
usage () {
|
|
cat <<EOF
|
|
usage: `basename $0` [options] command ...
|
|
|
|
Options:
|
|
-b basedir -- Operate on a system mounted at basedir
|
|
(default: /)
|
|
-d workdir -- Store working files in workdir
|
|
(default: /var/db/freebsd-update/)
|
|
-f conffile -- Read configuration options from conffile
|
|
(default: /etc/freebsd-update.conf)
|
|
-F -- Force a fetch operation to proceed in the
|
|
case of an unfinished upgrade
|
|
-j jail -- Operate on the given jail specified by jid or name
|
|
-k KEY -- Trust an RSA key with SHA256 hash of KEY
|
|
-r release -- Target for upgrade (e.g., 13.2-RELEASE)
|
|
-s server -- Server from which to fetch updates
|
|
(default: update.FreeBSD.org)
|
|
-t address -- Mail output of cron command, if any, to address
|
|
(default: root)
|
|
--not-running-from-cron
|
|
-- Run without a tty, for use by automated tools
|
|
--currently-running release
|
|
-- Update as if currently running this release
|
|
Commands:
|
|
fetch -- Fetch updates from server
|
|
cron -- Sleep rand(3600) seconds, fetch updates, and send an
|
|
email if updates were found
|
|
upgrade -- Fetch upgrades to FreeBSD version specified via -r option
|
|
updatesready -- Check if there are fetched updates ready to install
|
|
install -- Install downloaded updates or upgrades
|
|
rollback -- Uninstall most recently installed updates
|
|
IDS -- Compare the system against an index of "known good" files
|
|
showconfig -- Show configuration
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
#### Configuration processing functions
|
|
|
|
#-
|
|
# Configuration options are set in the following order of priority:
|
|
# 1. Command line options
|
|
# 2. Configuration file options
|
|
# 3. Default options
|
|
# In addition, certain options (e.g., IgnorePaths) can be specified multiple
|
|
# times and (as long as these are all in the same place, e.g., inside the
|
|
# configuration file) they will accumulate. Finally, because the path to the
|
|
# configuration file can be specified at the command line, the entire command
|
|
# line must be processed before we start reading the configuration file.
|
|
#
|
|
# Sound like a mess? It is. Here's how we handle this:
|
|
# 1. Initialize CONFFILE and all the options to "".
|
|
# 2. Process the command line. Throw an error if a non-accumulating option
|
|
# is specified twice.
|
|
# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
|
|
# 4. For all the configuration options X, set X_saved to X.
|
|
# 5. Initialize all the options to "".
|
|
# 6. Read CONFFILE line by line, parsing options.
|
|
# 7. For each configuration option X, set X to X_saved iff X_saved is not "".
|
|
# 8. Repeat steps 4-7, except setting options to their default values at (6).
|
|
|
|
CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
|
|
KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
|
|
BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
|
|
IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
|
|
|
|
# Set all the configuration options to "".
|
|
nullconfig () {
|
|
for X in ${CONFIGOPTIONS}; do
|
|
eval ${X}=""
|
|
done
|
|
}
|
|
|
|
# For each configuration option X, set X_saved to X.
|
|
saveconfig () {
|
|
for X in ${CONFIGOPTIONS}; do
|
|
eval ${X}_saved=\$${X}
|
|
done
|
|
}
|
|
|
|
# For each configuration option X, set X to X_saved if X_saved is not "".
|
|
mergeconfig () {
|
|
for X in ${CONFIGOPTIONS}; do
|
|
eval _=\$${X}_saved
|
|
if ! [ -z "${_}" ]; then
|
|
eval ${X}=\$${X}_saved
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Set the trusted keyprint.
|
|
config_KeyPrint () {
|
|
if [ -z ${KEYPRINT} ]; then
|
|
KEYPRINT=$1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set the working directory.
|
|
config_WorkDir () {
|
|
if [ -z ${WORKDIR} ]; then
|
|
WORKDIR=$1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set the name of the server (pool) from which to fetch updates
|
|
config_ServerName () {
|
|
if [ -z ${SERVERNAME} ]; then
|
|
SERVERNAME=$1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set the address to which 'cron' output will be mailed.
|
|
config_MailTo () {
|
|
if [ -z ${MAILTO} ]; then
|
|
MAILTO=$1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set whether FreeBSD Update is allowed to add files (or directories, or
|
|
# symlinks) which did not previously exist.
|
|
config_AllowAdd () {
|
|
if [ -z ${ALLOWADD} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
ALLOWADD=yes
|
|
;;
|
|
[Nn][Oo])
|
|
ALLOWADD=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
|
|
config_AllowDelete () {
|
|
if [ -z ${ALLOWDELETE} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
ALLOWDELETE=yes
|
|
;;
|
|
[Nn][Oo])
|
|
ALLOWDELETE=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Set whether FreeBSD Update should keep existing inode ownership,
|
|
# permissions, and flags, in the event that they have been modified locally
|
|
# after the release.
|
|
config_KeepModifiedMetadata () {
|
|
if [ -z ${KEEPMODIFIEDMETADATA} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
KEEPMODIFIEDMETADATA=yes
|
|
;;
|
|
[Nn][Oo])
|
|
KEEPMODIFIEDMETADATA=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Add to the list of components which should be kept updated.
|
|
config_Components () {
|
|
for C in $@; do
|
|
COMPONENTS="${COMPONENTS} ${C}"
|
|
done
|
|
}
|
|
|
|
# Remove src component from list if it isn't installed
|
|
finalize_components_config () {
|
|
COMPONENTS=""
|
|
for C in $@; do
|
|
if [ "$C" = "src" ]; then
|
|
if [ -e "${BASEDIR}/usr/src/COPYRIGHT" ]; then
|
|
COMPONENTS="${COMPONENTS} ${C}"
|
|
else
|
|
echo "src component not installed, skipped"
|
|
fi
|
|
else
|
|
COMPONENTS="${COMPONENTS} ${C}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Add to the list of paths under which updates will be ignored.
|
|
config_IgnorePaths () {
|
|
for C in $@; do
|
|
IGNOREPATHS="${IGNOREPATHS} ${C}"
|
|
done
|
|
}
|
|
|
|
# Add to the list of paths which IDS should ignore.
|
|
config_IDSIgnorePaths () {
|
|
for C in $@; do
|
|
IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}"
|
|
done
|
|
}
|
|
|
|
# Add to the list of paths within which updates will be performed only if the
|
|
# file on disk has not been modified locally.
|
|
config_UpdateIfUnmodified () {
|
|
for C in $@; do
|
|
UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
|
|
done
|
|
}
|
|
|
|
# Add to the list of paths within which updates to text files will be merged
|
|
# instead of overwritten.
|
|
config_MergeChanges () {
|
|
for C in $@; do
|
|
MERGECHANGES="${MERGECHANGES} ${C}"
|
|
done
|
|
}
|
|
|
|
# Work on a FreeBSD installation mounted under $1
|
|
config_BaseDir () {
|
|
if [ -z ${BASEDIR} ]; then
|
|
BASEDIR=$1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# When fetching upgrades, should we assume the user wants exactly the
|
|
# components listed in COMPONENTS, rather than trying to guess based on
|
|
# what's currently installed?
|
|
config_StrictComponents () {
|
|
if [ -z ${STRICTCOMPONENTS} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
STRICTCOMPONENTS=yes
|
|
;;
|
|
[Nn][Oo])
|
|
STRICTCOMPONENTS=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Upgrade to FreeBSD $1
|
|
config_TargetRelease () {
|
|
if [ -z ${TARGETRELEASE} ]; then
|
|
TARGETRELEASE=$1
|
|
else
|
|
return 1
|
|
fi
|
|
if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
|
|
TARGETRELEASE="${TARGETRELEASE}-RELEASE"
|
|
fi
|
|
}
|
|
|
|
# Pretend current release is FreeBSD $1
|
|
config_SourceRelease () {
|
|
UNAME_r=$1
|
|
if echo ${UNAME_r} | grep -qE '^[0-9.]+$'; then
|
|
UNAME_r="${UNAME_r}-RELEASE"
|
|
fi
|
|
export UNAME_r
|
|
}
|
|
|
|
# Get the Jail's path and the version of its installed userland
|
|
config_TargetJail () {
|
|
JAIL=$1
|
|
UNAME_r=$(freebsd-version -j ${JAIL})
|
|
BASEDIR=$(jls -j ${JAIL} -h path | awk 'NR == 2 {print}')
|
|
if [ -z ${BASEDIR} ] || [ -z ${UNAME_r} ]; then
|
|
echo "The specified jail either doesn't exist or" \
|
|
"does not have freebsd-version."
|
|
exit 1
|
|
fi
|
|
export UNAME_r
|
|
}
|
|
|
|
# Define what happens to output of utilities
|
|
config_VerboseLevel () {
|
|
if [ -z ${VERBOSELEVEL} ]; then
|
|
case $1 in
|
|
[Dd][Ee][Bb][Uu][Gg])
|
|
VERBOSELEVEL=debug
|
|
;;
|
|
[Nn][Oo][Ss][Tt][Aa][Tt][Ss])
|
|
VERBOSELEVEL=nostats
|
|
;;
|
|
[Ss][Tt][Aa][Tt][Ss])
|
|
VERBOSELEVEL=stats
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
config_BackupKernel () {
|
|
if [ -z ${BACKUPKERNEL} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
BACKUPKERNEL=yes
|
|
;;
|
|
[Nn][Oo])
|
|
BACKUPKERNEL=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
config_BackupKernelDir () {
|
|
if [ -z ${BACKUPKERNELDIR} ]; then
|
|
if [ -z "$1" ]; then
|
|
echo "BackupKernelDir set to empty dir"
|
|
return 1
|
|
fi
|
|
|
|
# We check for some paths which would be extremely odd
|
|
# to use, but which could cause a lot of problems if
|
|
# used.
|
|
case $1 in
|
|
/|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
|
|
echo "BackupKernelDir set to invalid path $1"
|
|
return 1
|
|
;;
|
|
/*)
|
|
BACKUPKERNELDIR=$1
|
|
;;
|
|
*)
|
|
echo "BackupKernelDir ($1) is not an absolute path"
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
config_BackupKernelSymbolFiles () {
|
|
if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
BACKUPKERNELSYMBOLFILES=yes
|
|
;;
|
|
[Nn][Oo])
|
|
BACKUPKERNELSYMBOLFILES=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
config_CreateBootEnv () {
|
|
if [ -z ${BOOTENV} ]; then
|
|
case $1 in
|
|
[Yy][Ee][Ss])
|
|
BOOTENV=yes
|
|
;;
|
|
[Nn][Oo])
|
|
BOOTENV=no
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
# Handle one line of configuration
|
|
configline () {
|
|
if [ $# -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
OPT=$1
|
|
shift
|
|
config_${OPT} $@
|
|
}
|
|
|
|
#### Parameter handling functions.
|
|
|
|
# Initialize parameters to null, just in case they're
|
|
# set in the environment.
|
|
init_params () {
|
|
# Configration settings
|
|
nullconfig
|
|
|
|
# No configuration file set yet
|
|
CONFFILE=""
|
|
|
|
# No commands specified yet
|
|
COMMANDS=""
|
|
|
|
# Force fetch to proceed
|
|
FORCEFETCH=0
|
|
|
|
# Run without a TTY
|
|
NOTTYOK=0
|
|
|
|
# Fetched first in a chain of commands
|
|
ISFETCHED=0
|
|
}
|
|
|
|
# Parse the command line
|
|
parse_cmdline () {
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
# Location of configuration file
|
|
-f)
|
|
if [ $# -eq 1 ]; then usage; fi
|
|
if [ ! -z "${CONFFILE}" ]; then usage; fi
|
|
shift; CONFFILE="$1"
|
|
;;
|
|
-F)
|
|
FORCEFETCH=1
|
|
;;
|
|
--not-running-from-cron)
|
|
NOTTYOK=1
|
|
;;
|
|
--currently-running)
|
|
shift
|
|
config_SourceRelease $1 || usage
|
|
;;
|
|
|
|
# Configuration file equivalents
|
|
-b)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_BaseDir $1 || usage
|
|
;;
|
|
-d)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_WorkDir $1 || usage
|
|
;;
|
|
-j)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_TargetJail $1 || usage
|
|
;;
|
|
-k)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_KeyPrint $1 || usage
|
|
;;
|
|
-s)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_ServerName $1 || usage
|
|
;;
|
|
-r)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_TargetRelease $1 || usage
|
|
;;
|
|
-t)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_MailTo $1 || usage
|
|
;;
|
|
-v)
|
|
if [ $# -eq 1 ]; then usage; fi; shift
|
|
config_VerboseLevel $1 || usage
|
|
;;
|
|
|
|
# Aliases for "-v debug" and "-v nostats"
|
|
--debug)
|
|
config_VerboseLevel debug || usage
|
|
;;
|
|
--no-stats)
|
|
config_VerboseLevel nostats || usage
|
|
;;
|
|
|
|
# Commands
|
|
cron | fetch | upgrade | updatesready | install | rollback |\
|
|
IDS | showconfig)
|
|
COMMANDS="${COMMANDS} $1"
|
|
;;
|
|
|
|
# Anything else is an error
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Make sure we have at least one command
|
|
if [ -z "${COMMANDS}" ]; then
|
|
usage
|
|
fi
|
|
}
|
|
|
|
# Parse the configuration file
|
|
parse_conffile () {
|
|
# If a configuration file was specified on the command line, check
|
|
# that it exists and is readable.
|
|
if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
|
|
echo -n "File does not exist "
|
|
echo -n "or is not readable: "
|
|
echo ${CONFFILE}
|
|
exit 1
|
|
fi
|
|
|
|
# If a configuration file was not specified on the command line,
|
|
# use the default configuration file path. If that default does
|
|
# not exist, give up looking for any configuration.
|
|
if [ -z "${CONFFILE}" ]; then
|
|
CONFFILE="/etc/freebsd-update.conf"
|
|
if [ ! -r "${CONFFILE}" ]; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Save the configuration options specified on the command line, and
|
|
# clear all the options in preparation for reading the config file.
|
|
saveconfig
|
|
nullconfig
|
|
|
|
# Read the configuration file. Anything after the first '#' is
|
|
# ignored, and any blank lines are ignored.
|
|
L=0
|
|
while read LINE; do
|
|
L=$(($L + 1))
|
|
LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
|
|
if ! configline ${LINEX}; then
|
|
echo "Error processing configuration file, line $L:"
|
|
echo "==> ${LINE}"
|
|
exit 1
|
|
fi
|
|
done < ${CONFFILE}
|
|
|
|
# Merge the settings read from the configuration file with those
|
|
# provided at the command line.
|
|
mergeconfig
|
|
}
|
|
|
|
# Provide some default parameters
|
|
default_params () {
|
|
# Save any parameters already configured, and clear the slate
|
|
saveconfig
|
|
nullconfig
|
|
|
|
# Default configurations
|
|
config_WorkDir /var/db/freebsd-update
|
|
config_MailTo root
|
|
config_AllowAdd yes
|
|
config_AllowDelete yes
|
|
config_KeepModifiedMetadata yes
|
|
config_BaseDir /
|
|
config_VerboseLevel stats
|
|
config_StrictComponents no
|
|
config_BackupKernel yes
|
|
config_BackupKernelDir /boot/kernel.old
|
|
config_BackupKernelSymbolFiles no
|
|
config_CreateBootEnv yes
|
|
|
|
# Merge these defaults into the earlier-configured settings
|
|
mergeconfig
|
|
}
|
|
|
|
# Set utility output filtering options, based on ${VERBOSELEVEL}
|
|
fetch_setup_verboselevel () {
|
|
case ${VERBOSELEVEL} in
|
|
debug)
|
|
QUIETREDIR="/dev/stderr"
|
|
QUIETFLAG=" "
|
|
STATSREDIR="/dev/stderr"
|
|
DDSTATS=".."
|
|
XARGST="-t"
|
|
NDEBUG=" "
|
|
;;
|
|
nostats)
|
|
QUIETREDIR=""
|
|
QUIETFLAG=""
|
|
STATSREDIR="/dev/null"
|
|
DDSTATS=".."
|
|
XARGST=""
|
|
NDEBUG=""
|
|
;;
|
|
stats)
|
|
QUIETREDIR="/dev/null"
|
|
QUIETFLAG="-q"
|
|
STATSREDIR="/dev/stdout"
|
|
DDSTATS=""
|
|
XARGST=""
|
|
NDEBUG="-n"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Perform sanity checks and set some final parameters
|
|
# in preparation for fetching files. Figure out which
|
|
# set of updates should be downloaded: If the user is
|
|
# running *-p[0-9]+, strip off the last part; if the
|
|
# user is running -SECURITY, call it -RELEASE. Chdir
|
|
# into the working directory.
|
|
fetchupgrade_check_params () {
|
|
export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
|
|
|
|
_SERVERNAME_z=\
|
|
"SERVERNAME must be given via command line or configuration file."
|
|
_KEYPRINT_z="Key must be given via -k option or configuration file."
|
|
_KEYPRINT_bad="Invalid key fingerprint: "
|
|
_WORKDIR_bad="Directory does not exist or is not writable: "
|
|
_WORKDIR_bad2="Directory is not on a persistent filesystem: "
|
|
|
|
if [ -z "${SERVERNAME}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "${_SERVERNAME_z}"
|
|
exit 1
|
|
fi
|
|
if [ -z "${KEYPRINT}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "${_KEYPRINT_z}"
|
|
exit 1
|
|
fi
|
|
if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_KEYPRINT_bad}"
|
|
echo ${KEYPRINT}
|
|
exit 1
|
|
fi
|
|
if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_WORKDIR_bad}"
|
|
echo ${WORKDIR}
|
|
exit 1
|
|
fi
|
|
case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*)
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_WORKDIR_bad2}"
|
|
echo ${WORKDIR}
|
|
exit 1
|
|
;;
|
|
esac
|
|
chmod 700 ${WORKDIR}
|
|
cd ${WORKDIR} || exit 1
|
|
|
|
# Generate release number. The s/SECURITY/RELEASE/ bit exists
|
|
# to provide an upgrade path for FreeBSD Update 1.x users, since
|
|
# the kernels provided by FreeBSD Update 1.x are always labelled
|
|
# as X.Y-SECURITY.
|
|
RELNUM=`uname -r |
|
|
sed -E 's,-p[0-9]+,,' |
|
|
sed -E 's,-SECURITY,-RELEASE,'`
|
|
ARCH=`uname -m`
|
|
FETCHDIR=${RELNUM}/${ARCH}
|
|
PATCHDIR=${RELNUM}/${ARCH}/bp
|
|
|
|
# Disallow upgrade from a version that is not a release
|
|
case ${RELNUM} in
|
|
*-RELEASE | *-ALPHA* | *-BETA* | *-RC*)
|
|
;;
|
|
*)
|
|
echo -n "`basename $0`: "
|
|
cat <<- EOF
|
|
Cannot upgrade from a version that is not a release
|
|
(including alpha, beta and release candidates)
|
|
using `basename $0`. Instead, FreeBSD can be directly
|
|
upgraded by source or upgraded to a RELEASE/RELENG version
|
|
prior to running `basename $0`.
|
|
Currently running: ${RELNUM}
|
|
EOF
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Figure out what directory contains the running kernel
|
|
BOOTFILE=`sysctl -n kern.bootfile`
|
|
KERNELDIR=${BOOTFILE%/kernel}
|
|
if ! [ -d ${KERNELDIR} ]; then
|
|
echo "Cannot identify running kernel"
|
|
exit 1
|
|
fi
|
|
|
|
# Figure out what kernel configuration is running. We start with
|
|
# the output of `uname -i`, and then make the following adjustments:
|
|
# 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config
|
|
# file says "ident SMP-GENERIC", I don't know...
|
|
# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
|
|
# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
|
|
# we're running an SMP kernel. This mis-identification is a bug
|
|
# which was fixed in 6.2-STABLE.
|
|
KERNCONF=`uname -i`
|
|
if [ ${KERNCONF} = "SMP-GENERIC" ]; then
|
|
KERNCONF=SMP
|
|
fi
|
|
if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
|
|
if sysctl kern.version | grep -qE '/SMP$'; then
|
|
KERNCONF=SMP
|
|
fi
|
|
fi
|
|
|
|
# Define some paths
|
|
BSPATCH=/usr/bin/bspatch
|
|
SHA256=/sbin/sha256
|
|
PHTTPGET=/usr/libexec/phttpget
|
|
|
|
# Set up variables relating to VERBOSELEVEL
|
|
fetch_setup_verboselevel
|
|
|
|
# Construct a unique name from ${BASEDIR}
|
|
BDHASH=`echo ${BASEDIR} | sha256 -q`
|
|
}
|
|
|
|
# Perform sanity checks etc. before fetching updates.
|
|
fetch_check_params () {
|
|
fetchupgrade_check_params
|
|
|
|
if ! [ -z "${TARGETRELEASE}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "-r option is meaningless with 'fetch' command. "
|
|
echo "(Did you mean 'upgrade' instead?)"
|
|
exit 1
|
|
fi
|
|
|
|
# Check that we have updates ready to install
|
|
if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
|
|
echo "You have a partially completed upgrade pending"
|
|
echo "Run '$0 install' first."
|
|
echo "Run '$0 fetch -F' to proceed anyway."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Perform sanity checks etc. before fetching upgrades.
|
|
upgrade_check_params () {
|
|
fetchupgrade_check_params
|
|
|
|
# Unless set otherwise, we're upgrading to the same kernel config.
|
|
NKERNCONF=${KERNCONF}
|
|
|
|
# We need TARGETRELEASE set
|
|
_TARGETRELEASE_z="Release target must be specified via -r option."
|
|
if [ -z "${TARGETRELEASE}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "${_TARGETRELEASE_z}"
|
|
exit 1
|
|
fi
|
|
|
|
# The target release should be != the current release.
|
|
if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "Cannot upgrade from ${RELNUM} to itself"
|
|
exit 1
|
|
fi
|
|
|
|
# Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
|
|
if [ "${ALLOWADD}" = "no" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "WARNING: \"AllowAdd no\" is a bad idea "
|
|
echo "when upgrading between releases."
|
|
echo
|
|
fi
|
|
if [ "${ALLOWDELETE}" = "no" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "WARNING: \"AllowDelete no\" is a bad idea "
|
|
echo "when upgrading between releases."
|
|
echo
|
|
fi
|
|
|
|
# Set EDITOR to /usr/bin/vi if it isn't already set
|
|
: ${EDITOR:='/usr/bin/vi'}
|
|
}
|
|
|
|
# Perform sanity checks and set some final parameters in
|
|
# preparation for installing updates.
|
|
install_check_params () {
|
|
# Check that we are root. All sorts of things won't work otherwise.
|
|
if [ `id -u` != 0 ]; then
|
|
echo "You must be root to run this."
|
|
exit 1
|
|
fi
|
|
|
|
# Check that securelevel <= 0. Otherwise we can't update schg files.
|
|
if [ `sysctl -n kern.securelevel` -gt 0 ]; then
|
|
echo "Updates cannot be installed when the system securelevel"
|
|
echo "is greater than zero."
|
|
exit 1
|
|
fi
|
|
|
|
# Check that we have a working directory
|
|
_WORKDIR_bad="Directory does not exist or is not writable: "
|
|
if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_WORKDIR_bad}"
|
|
echo ${WORKDIR}
|
|
exit 1
|
|
fi
|
|
cd ${WORKDIR} || exit 1
|
|
|
|
# Construct a unique name from ${BASEDIR}
|
|
BDHASH=`echo ${BASEDIR} | sha256 -q`
|
|
|
|
# Check that we have updates ready to install
|
|
if ! [ -L ${BDHASH}-install ]; then
|
|
echo "No updates are available to install."
|
|
if [ $ISFETCHED -eq 0 ]; then
|
|
echo "Run '$0 fetch' first."
|
|
exit 2
|
|
fi
|
|
exit 0
|
|
fi
|
|
if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
|
|
! [ -f ${BDHASH}-install/INDEX-NEW ]; then
|
|
echo "Update manifest is corrupt -- this should never happen."
|
|
echo "Re-run '$0 fetch'."
|
|
exit 1
|
|
fi
|
|
|
|
# Figure out what directory contains the running kernel
|
|
BOOTFILE=`sysctl -n kern.bootfile`
|
|
KERNELDIR=${BOOTFILE%/kernel}
|
|
if ! [ -d ${KERNELDIR} ]; then
|
|
echo "Cannot identify running kernel"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Creates a new boot environment
|
|
install_create_be () {
|
|
# Figure out if we're running in a jail and return if we are
|
|
if [ `sysctl -n security.jail.jailed` = 1 ]; then
|
|
return 1
|
|
fi
|
|
# Operating on roots that aren't located at / will, more often than not,
|
|
# not touch the boot environment.
|
|
if [ "$BASEDIR" != "/" ]; then
|
|
return 1
|
|
fi
|
|
# Create a boot environment if enabled
|
|
if [ ${BOOTENV} = yes ]; then
|
|
bectl check 2>/dev/null
|
|
case $? in
|
|
0)
|
|
# Boot environment are supported
|
|
CREATEBE=yes
|
|
;;
|
|
255)
|
|
# Boot environments are not supported
|
|
CREATEBE=no
|
|
;;
|
|
*)
|
|
# If bectl returns an unexpected exit code, don't create a BE
|
|
CREATEBE=no
|
|
;;
|
|
esac
|
|
if [ ${CREATEBE} = yes ]; then
|
|
echo -n "Creating snapshot of existing boot environment... "
|
|
VERSION=`freebsd-version -ku | sort -V | tail -n 1`
|
|
TIMESTAMP=`date +"%Y-%m-%d_%H%M%S"`
|
|
bectl create ${VERSION}_${TIMESTAMP}
|
|
if [ $? -eq 0 ]; then
|
|
echo "done.";
|
|
else
|
|
echo "failed."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Perform sanity checks and set some final parameters in
|
|
# preparation for UNinstalling updates.
|
|
rollback_check_params () {
|
|
# Check that we are root. All sorts of things won't work otherwise.
|
|
if [ `id -u` != 0 ]; then
|
|
echo "You must be root to run this."
|
|
exit 1
|
|
fi
|
|
|
|
# Check that we have a working directory
|
|
_WORKDIR_bad="Directory does not exist or is not writable: "
|
|
if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_WORKDIR_bad}"
|
|
echo ${WORKDIR}
|
|
exit 1
|
|
fi
|
|
cd ${WORKDIR} || exit 1
|
|
|
|
# Construct a unique name from ${BASEDIR}
|
|
BDHASH=`echo ${BASEDIR} | sha256 -q`
|
|
|
|
# Check that we have updates ready to rollback
|
|
if ! [ -L ${BDHASH}-rollback ]; then
|
|
echo "No rollback directory found."
|
|
exit 1
|
|
fi
|
|
if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
|
|
! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
|
|
echo "Update manifest is corrupt -- this should never happen."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Perform sanity checks and set some final parameters
|
|
# in preparation for comparing the system against the
|
|
# published index. Figure out which index we should
|
|
# compare against: If the user is running *-p[0-9]+,
|
|
# strip off the last part; if the user is running
|
|
# -SECURITY, call it -RELEASE. Chdir into the working
|
|
# directory.
|
|
IDS_check_params () {
|
|
export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
|
|
|
|
_SERVERNAME_z=\
|
|
"SERVERNAME must be given via command line or configuration file."
|
|
_KEYPRINT_z="Key must be given via -k option or configuration file."
|
|
_KEYPRINT_bad="Invalid key fingerprint: "
|
|
_WORKDIR_bad="Directory does not exist or is not writable: "
|
|
|
|
if [ -z "${SERVERNAME}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "${_SERVERNAME_z}"
|
|
exit 1
|
|
fi
|
|
if [ -z "${KEYPRINT}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo "${_KEYPRINT_z}"
|
|
exit 1
|
|
fi
|
|
if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_KEYPRINT_bad}"
|
|
echo ${KEYPRINT}
|
|
exit 1
|
|
fi
|
|
if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
|
|
echo -n "`basename $0`: "
|
|
echo -n "${_WORKDIR_bad}"
|
|
echo ${WORKDIR}
|
|
exit 1
|
|
fi
|
|
cd ${WORKDIR} || exit 1
|
|
|
|
# Generate release number. The s/SECURITY/RELEASE/ bit exists
|
|
# to provide an upgrade path for FreeBSD Update 1.x users, since
|
|
# the kernels provided by FreeBSD Update 1.x are always labelled
|
|
# as X.Y-SECURITY.
|
|
RELNUM=`uname -r |
|
|
sed -E 's,-p[0-9]+,,' |
|
|
sed -E 's,-SECURITY,-RELEASE,'`
|
|
ARCH=`uname -m`
|
|
FETCHDIR=${RELNUM}/${ARCH}
|
|
PATCHDIR=${RELNUM}/${ARCH}/bp
|
|
|
|
# Figure out what directory contains the running kernel
|
|
BOOTFILE=`sysctl -n kern.bootfile`
|
|
KERNELDIR=${BOOTFILE%/kernel}
|
|
if ! [ -d ${KERNELDIR} ]; then
|
|
echo "Cannot identify running kernel"
|
|
exit 1
|
|
fi
|
|
|
|
# Figure out what kernel configuration is running. We start with
|
|
# the output of `uname -i`, and then make the following adjustments:
|
|
# 1. Replace "SMP-GENERIC" with "SMP". Why the SMP kernel config
|
|
# file says "ident SMP-GENERIC", I don't know...
|
|
# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
|
|
# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
|
|
# we're running an SMP kernel. This mis-identification is a bug
|
|
# which was fixed in 6.2-STABLE.
|
|
KERNCONF=`uname -i`
|
|
if [ ${KERNCONF} = "SMP-GENERIC" ]; then
|
|
KERNCONF=SMP
|
|
fi
|
|
if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
|
|
if sysctl kern.version | grep -qE '/SMP$'; then
|
|
KERNCONF=SMP
|
|
fi
|
|
fi
|
|
|
|
# Define some paths
|
|
SHA256=/sbin/sha256
|
|
PHTTPGET=/usr/libexec/phttpget
|
|
|
|
# Set up variables relating to VERBOSELEVEL
|
|
fetch_setup_verboselevel
|
|
}
|
|
|
|
#### Core functionality -- the actual work gets done here
|
|
|
|
# Use an SRV query to pick a server. If the SRV query doesn't provide
|
|
# a useful answer, use the server name specified by the user.
|
|
# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
|
|
# from that; or if no servers are returned, use ${SERVERNAME}.
|
|
# This allows a user to specify "portsnap.freebsd.org" (in which case
|
|
# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
|
|
# (in which case portsnap will use that particular server, since there
|
|
# won't be an SRV entry for that name).
|
|
#
|
|
# We ignore the Port field, since we are always going to use port 80.
|
|
|
|
# Fetch the mirror list, but do not pick a mirror yet. Returns 1 if
|
|
# no mirrors are available for any reason.
|
|
fetch_pick_server_init () {
|
|
: > serverlist_tried
|
|
|
|
# Check that host(1) exists (i.e., that the system wasn't built with the
|
|
# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
|
|
if ! which -s host; then
|
|
: > serverlist_full
|
|
return 1
|
|
fi
|
|
|
|
echo -n "Looking up ${SERVERNAME} mirrors... "
|
|
|
|
# Issue the SRV query and pull out the Priority, Weight, and Target fields.
|
|
# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
|
|
# "$name server selection ..."; we allow either format.
|
|
MLIST="_http._tcp.${SERVERNAME}"
|
|
host -t srv "${MLIST}" |
|
|
sed -nE "s/${MLIST} (has SRV record|server selection) //Ip" |
|
|
cut -f 1,2,4 -d ' ' |
|
|
sed -e 's/\.$//' |
|
|
sort > serverlist_full
|
|
|
|
# If no records, give up -- we'll just use the server name we were given.
|
|
if [ `wc -l < serverlist_full` -eq 0 ]; then
|
|
echo "none found."
|
|
return 1
|
|
fi
|
|
|
|
# Report how many mirrors we found.
|
|
echo `wc -l < serverlist_full` "mirrors found."
|
|
|
|
# Generate a random seed for use in picking mirrors. If HTTP_PROXY
|
|
# is set, this will be used to generate the seed; otherwise, the seed
|
|
# will be random.
|
|
if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
|
|
RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
|
|
tr -d 'a-f' |
|
|
cut -c 1-9`
|
|
else
|
|
RANDVALUE=`jot -r 1 0 999999999`
|
|
fi
|
|
}
|
|
|
|
# Pick a mirror. Returns 1 if we have run out of mirrors to try.
|
|
fetch_pick_server () {
|
|
# Generate a list of not-yet-tried mirrors
|
|
sort serverlist_tried |
|
|
comm -23 serverlist_full - > serverlist
|
|
|
|
# Have we run out of mirrors?
|
|
if [ `wc -l < serverlist` -eq 0 ]; then
|
|
cat <<- EOF
|
|
No mirrors remaining, giving up.
|
|
|
|
This may be because upgrading from this platform (${ARCH})
|
|
or release (${RELNUM}) is unsupported by `basename $0`. Only
|
|
platforms with Tier 1 support can be upgraded by `basename $0`.
|
|
See https://www.freebsd.org/platforms/ for more info.
|
|
|
|
If unsupported, FreeBSD must be upgraded by source.
|
|
EOF
|
|
return 1
|
|
fi
|
|
|
|
# Find the highest priority level (lowest numeric value).
|
|
SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
|
|
|
|
# Add up the weights of the response lines at that priority level.
|
|
SRV_WSUM=0;
|
|
while read X; do
|
|
case "$X" in
|
|
${SRV_PRIORITY}\ *)
|
|
SRV_W=`echo $X | cut -f 2 -d ' '`
|
|
SRV_WSUM=$(($SRV_WSUM + $SRV_W))
|
|
;;
|
|
esac
|
|
done < serverlist
|
|
|
|
# If all the weights are 0, pretend that they are all 1 instead.
|
|
if [ ${SRV_WSUM} -eq 0 ]; then
|
|
SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
|
|
SRV_W_ADD=1
|
|
else
|
|
SRV_W_ADD=0
|
|
fi
|
|
|
|
# Pick a value between 0 and the sum of the weights - 1
|
|
SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
|
|
|
|
# Read through the list of mirrors and set SERVERNAME. Write the line
|
|
# corresponding to the mirror we selected into serverlist_tried so that
|
|
# we won't try it again.
|
|
while read X; do
|
|
case "$X" in
|
|
${SRV_PRIORITY}\ *)
|
|
SRV_W=`echo $X | cut -f 2 -d ' '`
|
|
SRV_W=$(($SRV_W + $SRV_W_ADD))
|
|
if [ $SRV_RND -lt $SRV_W ]; then
|
|
SERVERNAME=`echo $X | cut -f 3 -d ' '`
|
|
echo "$X" >> serverlist_tried
|
|
break
|
|
else
|
|
SRV_RND=$(($SRV_RND - $SRV_W))
|
|
fi
|
|
;;
|
|
esac
|
|
done < serverlist
|
|
}
|
|
|
|
# Take a list of ${oldhash}|${newhash} and output a list of needed patches,
|
|
# i.e., those for which we have ${oldhash} and don't have ${newhash}.
|
|
fetch_make_patchlist () {
|
|
grep -vE "^([0-9a-f]{64})\|\1$" |
|
|
tr '|' ' ' |
|
|
while read X Y; do
|
|
if [ -f "files/${Y}.gz" ] ||
|
|
[ ! -f "files/${X}.gz" ]; then
|
|
continue
|
|
fi
|
|
echo "${X}|${Y}"
|
|
done | sort -u
|
|
}
|
|
|
|
# Print user-friendly progress statistics
|
|
fetch_progress () {
|
|
LNC=0
|
|
while read x; do
|
|
LNC=$(($LNC + 1))
|
|
if [ $(($LNC % 10)) = 0 ]; then
|
|
echo -n $LNC
|
|
elif [ $(($LNC % 2)) = 0 ]; then
|
|
echo -n .
|
|
fi
|
|
done
|
|
echo -n " "
|
|
}
|
|
|
|
# Function for asking the user if everything is ok
|
|
continuep () {
|
|
while read -p "Does this look reasonable (y/n)? " CONTINUE; do
|
|
case "${CONTINUE}" in
|
|
y*)
|
|
return 0
|
|
;;
|
|
n*)
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Initialize the working directory
|
|
workdir_init () {
|
|
mkdir -p files
|
|
touch tINDEX.present
|
|
}
|
|
|
|
# Check that we have a public key with an appropriate hash, or
|
|
# fetch the key if it doesn't exist. Returns 1 if the key has
|
|
# not yet been fetched.
|
|
fetch_key () {
|
|
if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
|
|
return 0
|
|
fi
|
|
|
|
echo -n "Fetching public key from ${SERVERNAME}... "
|
|
rm -f pub.ssl
|
|
fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
|
|
2>${QUIETREDIR} || true
|
|
if ! [ -r pub.ssl ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
|
|
echo "key has incorrect hash."
|
|
rm -f pub.ssl
|
|
return 1
|
|
fi
|
|
echo "done."
|
|
}
|
|
|
|
# Fetch metadata signature, aka "tag".
|
|
fetch_tag () {
|
|
echo -n "Fetching metadata signature "
|
|
echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
|
|
rm -f latest.ssl
|
|
fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl \
|
|
2>${QUIETREDIR} || true
|
|
if ! [ -r latest.ssl ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
|
|
openssl rsautl -pubin -inkey pub.ssl -verify \
|
|
< latest.ssl > tag.new 2>${QUIETREDIR} || true
|
|
rm latest.ssl
|
|
|
|
if ! [ `wc -l < tag.new` = 1 ] ||
|
|
! grep -qE \
|
|
"^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
|
|
tag.new; then
|
|
echo "invalid signature."
|
|
return 1
|
|
fi
|
|
|
|
echo "done."
|
|
|
|
RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
|
|
TINDEXHASH=`cut -f 5 -d '|' < tag.new`
|
|
EOLTIME=`cut -f 6 -d '|' < tag.new`
|
|
}
|
|
|
|
# Sanity-check the patch number in a tag, to make sure that we're not
|
|
# going to "update" backwards and to prevent replay attacks.
|
|
fetch_tagsanity () {
|
|
# Check that we're not going to move from -pX to -pY with Y < X.
|
|
RELPX=`uname -r | sed -E 's,.*-,,'`
|
|
if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
|
|
RELPX=`echo ${RELPX} | cut -c 2-`
|
|
else
|
|
RELPX=0
|
|
fi
|
|
if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
|
|
echo
|
|
echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
|
|
echo " appear older than what"
|
|
echo "we are currently running (`uname -r`)!"
|
|
echo "Cowardly refusing to proceed any further."
|
|
return 1
|
|
fi
|
|
|
|
# If "tag" exists and corresponds to ${RELNUM}, make sure that
|
|
# it contains a patch number <= RELPATCHNUM, in order to protect
|
|
# against rollback (replay) attacks.
|
|
if [ -f tag ] &&
|
|
grep -qE \
|
|
"^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
|
|
tag; then
|
|
LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
|
|
|
|
if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
|
|
echo
|
|
echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
|
|
echo " are older than the"
|
|
echo -n "most recently seen updates"
|
|
echo " (${RELNUM}-p${LASTRELPATCHNUM})."
|
|
echo "Cowardly refusing to proceed any further."
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Fetch metadata index file
|
|
fetch_metadata_index () {
|
|
echo ${NDEBUG} "Fetching metadata index... "
|
|
rm -f ${TINDEXHASH}
|
|
fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
|
|
2>${QUIETREDIR}
|
|
if ! [ -f ${TINDEXHASH} ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
|
|
echo "update metadata index corrupt."
|
|
return 1
|
|
fi
|
|
echo "done."
|
|
}
|
|
|
|
# Print an error message about signed metadata being bogus.
|
|
fetch_metadata_bogus () {
|
|
echo
|
|
echo "The update metadata$1 is correctly signed, but"
|
|
echo "failed an integrity check."
|
|
echo "Cowardly refusing to proceed any further."
|
|
return 1
|
|
}
|
|
|
|
# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
|
|
# with the lines not named in $@ from tINDEX.present (if that file exists).
|
|
fetch_metadata_index_merge () {
|
|
for METAFILE in $@; do
|
|
if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l` \
|
|
-ne 1 ]; then
|
|
fetch_metadata_bogus " index"
|
|
return 1
|
|
fi
|
|
|
|
grep -E "${METAFILE}\|" ${TINDEXHASH}
|
|
done |
|
|
sort > tINDEX.wanted
|
|
|
|
if [ -f tINDEX.present ]; then
|
|
join -t '|' -v 2 tINDEX.wanted tINDEX.present |
|
|
sort -m - tINDEX.wanted > tINDEX.new
|
|
rm tINDEX.wanted
|
|
else
|
|
mv tINDEX.wanted tINDEX.new
|
|
fi
|
|
}
|
|
|
|
# Sanity check all the lines of tINDEX.new. Even if more metadata lines
|
|
# are added by future versions of the server, this won't cause problems,
|
|
# since the only lines which appear in tINDEX.new are the ones which we
|
|
# specifically grepped out of ${TINDEXHASH}.
|
|
fetch_metadata_index_sanity () {
|
|
if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
|
|
fetch_metadata_bogus " index"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Sanity check the metadata file $1.
|
|
fetch_metadata_sanity () {
|
|
# Some aliases to save space later: ${P} is a character which can
|
|
# appear in a path; ${M} is the four numeric metadata fields; and
|
|
# ${H} is a sha256 hash.
|
|
P="[-+./:=,%@_[~[:alnum:]]"
|
|
M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
|
|
H="[0-9a-f]{64}"
|
|
|
|
# Check that the first four fields make sense.
|
|
if gunzip -c < files/$1.gz |
|
|
grep -qvE "^[a-z]+\|[0-9a-z-]+\|${P}+\|[fdL-]\|"; then
|
|
fetch_metadata_bogus ""
|
|
return 1
|
|
fi
|
|
|
|
# Remove the first three fields.
|
|
gunzip -c < files/$1.gz |
|
|
cut -f 4- -d '|' > sanitycheck.tmp
|
|
|
|
# Sanity check entries with type 'f'
|
|
if grep -E '^f' sanitycheck.tmp |
|
|
grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
|
|
fetch_metadata_bogus ""
|
|
return 1
|
|
fi
|
|
|
|
# Sanity check entries with type 'd'
|
|
if grep -E '^d' sanitycheck.tmp |
|
|
grep -qvE "^d\|${M}\|\|\$"; then
|
|
fetch_metadata_bogus ""
|
|
return 1
|
|
fi
|
|
|
|
# Sanity check entries with type 'L'
|
|
if grep -E '^L' sanitycheck.tmp |
|
|
grep -qvE "^L\|${M}\|${P}*\|\$"; then
|
|
fetch_metadata_bogus ""
|
|
return 1
|
|
fi
|
|
|
|
# Sanity check entries with type '-'
|
|
if grep -E '^-' sanitycheck.tmp |
|
|
grep -qvE "^-\|\|\|\|\|\|"; then
|
|
fetch_metadata_bogus ""
|
|
return 1
|
|
fi
|
|
|
|
# Clean up
|
|
rm sanitycheck.tmp
|
|
}
|
|
|
|
# Fetch the metadata index and metadata files listed in $@,
|
|
# taking advantage of metadata patches where possible.
|
|
fetch_metadata () {
|
|
fetch_metadata_index || return 1
|
|
fetch_metadata_index_merge $@ || return 1
|
|
fetch_metadata_index_sanity || return 1
|
|
|
|
# Generate a list of wanted metadata patches
|
|
join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
|
|
fetch_make_patchlist > patchlist
|
|
|
|
if [ -s patchlist ]; then
|
|
# Attempt to fetch metadata patches
|
|
echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
|
|
echo ${NDEBUG} "metadata patches.${DDSTATS}"
|
|
tr '|' '-' < patchlist |
|
|
lam -s "${FETCHDIR}/tp/" - -s ".gz" |
|
|
xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
|
|
2>${STATSREDIR} | fetch_progress
|
|
echo "done."
|
|
|
|
# Attempt to apply metadata patches
|
|
echo -n "Applying metadata patches... "
|
|
tr '|' ' ' < patchlist |
|
|
while read X Y; do
|
|
if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
|
|
gunzip -c < ${X}-${Y}.gz > diff
|
|
gunzip -c < files/${X}.gz > diff-OLD
|
|
|
|
# Figure out which lines are being added and removed
|
|
grep -E '^-' diff |
|
|
cut -c 2- |
|
|
while read PREFIX; do
|
|
look "${PREFIX}" diff-OLD
|
|
done |
|
|
sort > diff-rm
|
|
grep -E '^\+' diff |
|
|
cut -c 2- > diff-add
|
|
|
|
# Generate the new file
|
|
comm -23 diff-OLD diff-rm |
|
|
sort - diff-add > diff-NEW
|
|
|
|
if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
|
|
mv diff-NEW files/${Y}
|
|
gzip -n files/${Y}
|
|
else
|
|
mv diff-NEW ${Y}.bad
|
|
fi
|
|
rm -f ${X}-${Y}.gz diff
|
|
rm -f diff-OLD diff-NEW diff-add diff-rm
|
|
done 2>${QUIETREDIR}
|
|
echo "done."
|
|
fi
|
|
|
|
# Update metadata without patches
|
|
cut -f 2 -d '|' < tINDEX.new |
|
|
while read Y; do
|
|
if [ ! -f "files/${Y}.gz" ]; then
|
|
echo ${Y};
|
|
fi
|
|
done |
|
|
sort -u > filelist
|
|
|
|
if [ -s filelist ]; then
|
|
echo -n "Fetching `wc -l < filelist | tr -d ' '` "
|
|
echo ${NDEBUG} "metadata files... "
|
|
lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
|
|
xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
|
|
2>${QUIETREDIR}
|
|
|
|
while read Y; do
|
|
if ! [ -f ${Y}.gz ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
if [ `gunzip -c < ${Y}.gz |
|
|
${SHA256} -q` = ${Y} ]; then
|
|
mv ${Y}.gz files/${Y}.gz
|
|
else
|
|
echo "metadata is corrupt."
|
|
return 1
|
|
fi
|
|
done < filelist
|
|
echo "done."
|
|
fi
|
|
|
|
# Sanity-check the metadata files.
|
|
cut -f 2 -d '|' tINDEX.new > filelist
|
|
while read X; do
|
|
fetch_metadata_sanity ${X} || return 1
|
|
done < filelist
|
|
|
|
# Remove files which are no longer needed
|
|
cut -f 2 -d '|' tINDEX.present |
|
|
sort > oldfiles
|
|
cut -f 2 -d '|' tINDEX.new |
|
|
sort |
|
|
comm -13 - oldfiles |
|
|
lam -s "files/" - -s ".gz" |
|
|
xargs rm -f
|
|
rm patchlist filelist oldfiles
|
|
rm ${TINDEXHASH}
|
|
|
|
# We're done!
|
|
mv tINDEX.new tINDEX.present
|
|
mv tag.new tag
|
|
|
|
return 0
|
|
}
|
|
|
|
# Extract a subset of a downloaded metadata file containing only the parts
|
|
# which are listed in COMPONENTS.
|
|
fetch_filter_metadata_components () {
|
|
METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
|
|
gunzip -c < files/${METAHASH}.gz > $1.all
|
|
|
|
# Fish out the lines belonging to components we care about.
|
|
for C in ${COMPONENTS}; do
|
|
look "`echo ${C} | tr '/' '|'`|" $1.all
|
|
done > $1
|
|
|
|
# Remove temporary file.
|
|
rm $1.all
|
|
}
|
|
|
|
# Generate a filtered version of the metadata file $1 from the downloaded
|
|
# file, by fishing out the lines corresponding to components we're trying
|
|
# to keep updated, and then removing lines corresponding to paths we want
|
|
# to ignore.
|
|
fetch_filter_metadata () {
|
|
# Fish out the lines belonging to components we care about.
|
|
fetch_filter_metadata_components $1
|
|
|
|
# Canonicalize directory names by removing any trailing / in
|
|
# order to avoid listing directories multiple times if they
|
|
# belong to multiple components. Turning "/" into "" doesn't
|
|
# matter, since we add a leading "/" when we use paths later.
|
|
cut -f 3- -d '|' $1 |
|
|
sed -e 's,/|d|,|d|,' |
|
|
sed -e 's,/|-|,|-|,' |
|
|
sort -u > $1.tmp
|
|
|
|
# Figure out which lines to ignore and remove them.
|
|
for X in ${IGNOREPATHS}; do
|
|
grep -E "^${X}" $1.tmp
|
|
done |
|
|
sort -u |
|
|
comm -13 - $1.tmp > $1
|
|
|
|
# Remove temporary files.
|
|
rm $1.tmp
|
|
}
|
|
|
|
# Filter the metadata file $1 by adding lines with "/boot/$2"
|
|
# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
|
|
# trailing "/kernel"); and if "/boot/$2" does not exist, remove
|
|
# the original lines which start with that.
|
|
# Put another way: Deal with the fact that the FOO kernel is sometimes
|
|
# installed in /boot/FOO/ and is sometimes installed elsewhere.
|
|
fetch_filter_kernel_names () {
|
|
grep ^/boot/$2 $1 |
|
|
sed -e "s,/boot/$2,${KERNELDIR},g" |
|
|
sort - $1 > $1.tmp
|
|
mv $1.tmp $1
|
|
|
|
if ! [ -d /boot/$2 ]; then
|
|
grep -v ^/boot/$2 $1 > $1.tmp
|
|
mv $1.tmp $1
|
|
fi
|
|
}
|
|
|
|
# For all paths appearing in $1 or $3, inspect the system
|
|
# and generate $2 describing what is currently installed.
|
|
fetch_inspect_system () {
|
|
# No errors yet...
|
|
rm -f .err
|
|
|
|
# Tell the user why his disk is suddenly making lots of noise
|
|
echo -n "Inspecting system... "
|
|
|
|
# Generate list of files to inspect
|
|
cat $1 $3 |
|
|
cut -f 1 -d '|' |
|
|
sort -u > filelist
|
|
|
|
# Examine each file and output lines of the form
|
|
# /path/to/file|type|device-inum|user|group|perm|flags|value
|
|
# sorted by device and inode number.
|
|
while read F; do
|
|
# If the symlink/file/directory does not exist, record this.
|
|
if ! [ -e ${BASEDIR}/${F} ]; then
|
|
echo "${F}|-||||||"
|
|
continue
|
|
fi
|
|
if ! [ -r ${BASEDIR}/${F} ]; then
|
|
echo "Cannot read file: ${BASEDIR}/${F}" \
|
|
>/dev/stderr
|
|
touch .err
|
|
return 1
|
|
fi
|
|
|
|
# Otherwise, output an index line.
|
|
if [ -L ${BASEDIR}/${F} ]; then
|
|
echo -n "${F}|L|"
|
|
stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
|
|
readlink ${BASEDIR}/${F};
|
|
elif [ -f ${BASEDIR}/${F} ]; then
|
|
echo -n "${F}|f|"
|
|
stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
|
|
sha256 -q ${BASEDIR}/${F};
|
|
elif [ -d ${BASEDIR}/${F} ]; then
|
|
echo -n "${F}|d|"
|
|
stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
|
|
else
|
|
echo "Unknown file type: ${BASEDIR}/${F}" \
|
|
>/dev/stderr
|
|
touch .err
|
|
return 1
|
|
fi
|
|
done < filelist |
|
|
sort -k 3,3 -t '|' > $2.tmp
|
|
rm filelist
|
|
|
|
# Check if an error occurred during system inspection
|
|
if [ -f .err ]; then
|
|
return 1
|
|
fi
|
|
|
|
# Convert to the form
|
|
# /path/to/file|type|user|group|perm|flags|value|hlink
|
|
# by resolving identical device and inode numbers into hard links.
|
|
cut -f 1,3 -d '|' $2.tmp |
|
|
sort -k 1,1 -t '|' |
|
|
sort -s -u -k 2,2 -t '|' |
|
|
join -1 2 -2 3 -t '|' - $2.tmp |
|
|
awk -F \| -v OFS=\| \
|
|
'{
|
|
if (($2 == $3) || ($4 == "-"))
|
|
print $3,$4,$5,$6,$7,$8,$9,""
|
|
else
|
|
print $3,$4,$5,$6,$7,$8,$9,$2
|
|
}' |
|
|
sort > $2
|
|
rm $2.tmp
|
|
|
|
# We're finished looking around
|
|
echo "done."
|
|
}
|
|
|
|
# For any paths matching ${MERGECHANGES}, compare $2 against $1 and $3 and
|
|
# find any files with values unique to $2; generate $4 containing these paths
|
|
# and their corresponding hashes from $1.
|
|
fetch_filter_mergechanges () {
|
|
# Pull out the paths and hashes of the files matching ${MERGECHANGES}.
|
|
for F in $1 $2 $3; do
|
|
for X in ${MERGECHANGES}; do
|
|
grep -E "^${X}" ${F}
|
|
done |
|
|
cut -f 1,2,7 -d '|' |
|
|
sort > ${F}-values
|
|
done
|
|
|
|
# Any line in $2-values which doesn't appear in $1-values or $3-values
|
|
# and is a file means that we should list the path in $3.
|
|
sort $1-values $3-values |
|
|
comm -13 - $2-values |
|
|
fgrep '|f|' |
|
|
cut -f 1 -d '|' > $2-paths
|
|
|
|
# For each path, pull out one (and only one!) entry from $1-values.
|
|
# Note that we cannot distinguish which "old" version the user made
|
|
# changes to; but hopefully any changes which occur due to security
|
|
# updates will exist in both the "new" version and the version which
|
|
# the user has installed, so the merging will still work.
|
|
while read X; do
|
|
look "${X}|" $1-values |
|
|
head -1
|
|
done < $2-paths > $4
|
|
|
|
# Clean up
|
|
rm $1-values $2-values $3-values $2-paths
|
|
}
|
|
|
|
# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
|
|
# which correspond to lines in $2 with hashes not matching $1 or $3, unless
|
|
# the paths are listed in $4. For entries in $2 marked "not present"
|
|
# (aka. type -), remove lines from $[123] unless there is a corresponding
|
|
# entry in $1.
|
|
fetch_filter_unmodified_notpresent () {
|
|
# Figure out which lines of $1 and $3 correspond to bits which
|
|
# should only be updated if they haven't changed, and fish out
|
|
# the (path, type, value) tuples.
|
|
# NOTE: We don't consider a file to be "modified" if it matches
|
|
# the hash from $3.
|
|
for X in ${UPDATEIFUNMODIFIED}; do
|
|
grep -E "^${X}" $1
|
|
grep -E "^${X}" $3
|
|
done |
|
|
cut -f 1,2,7 -d '|' |
|
|
sort > $1-values
|
|
|
|
# Do the same for $2.
|
|
for X in ${UPDATEIFUNMODIFIED}; do
|
|
grep -E "^${X}" $2
|
|
done |
|
|
cut -f 1,2,7 -d '|' |
|
|
sort > $2-values
|
|
|
|
# Any entry in $2-values which is not in $1-values corresponds to
|
|
# a path which we need to remove from $1, $2, and $3, unless it
|
|
# that path appears in $4.
|
|
comm -13 $1-values $2-values |
|
|
sort -t '|' -k 1,1 > mlines.tmp
|
|
cut -f 1 -d '|' $4 |
|
|
sort |
|
|
join -v 2 -t '|' - mlines.tmp |
|
|
sort > mlines
|
|
rm $1-values $2-values mlines.tmp
|
|
|
|
# Any lines in $2 which are not in $1 AND are "not present" lines
|
|
# also belong in mlines.
|
|
comm -13 $1 $2 |
|
|
cut -f 1,2,7 -d '|' |
|
|
fgrep '|-|' >> mlines
|
|
|
|
# Remove lines from $1, $2, and $3
|
|
for X in $1 $2 $3; do
|
|
sort -t '|' -k 1,1 ${X} > ${X}.tmp
|
|
cut -f 1 -d '|' < mlines |
|
|
sort |
|
|
join -v 2 -t '|' - ${X}.tmp |
|
|
sort > ${X}
|
|
rm ${X}.tmp
|
|
done
|
|
|
|
# Store a list of the modified files, for future reference
|
|
fgrep -v '|-|' mlines |
|
|
cut -f 1 -d '|' > modifiedfiles
|
|
rm mlines
|
|
}
|
|
|
|
# For each entry in $1 of type -, remove any corresponding
|
|
# entry from $2 if ${ALLOWADD} != "yes". Remove all entries
|
|
# of type - from $1.
|
|
fetch_filter_allowadd () {
|
|
cut -f 1,2 -d '|' < $1 |
|
|
fgrep '|-' |
|
|
cut -f 1 -d '|' > filesnotpresent
|
|
|
|
if [ ${ALLOWADD} != "yes" ]; then
|
|
sort < $2 |
|
|
join -v 1 -t '|' - filesnotpresent |
|
|
sort > $2.tmp
|
|
mv $2.tmp $2
|
|
fi
|
|
|
|
sort < $1 |
|
|
join -v 1 -t '|' - filesnotpresent |
|
|
sort > $1.tmp
|
|
mv $1.tmp $1
|
|
rm filesnotpresent
|
|
}
|
|
|
|
# If ${ALLOWDELETE} != "yes", then remove any entries from $1
|
|
# which don't correspond to entries in $2.
|
|
fetch_filter_allowdelete () {
|
|
# Produce a lists ${PATH}|${TYPE}
|
|
for X in $1 $2; do
|
|
cut -f 1-2 -d '|' < ${X} |
|
|
sort -u > ${X}.nodes
|
|
done
|
|
|
|
# Figure out which lines need to be removed from $1.
|
|
if [ ${ALLOWDELETE} != "yes" ]; then
|
|
comm -23 $1.nodes $2.nodes > $1.badnodes
|
|
else
|
|
: > $1.badnodes
|
|
fi
|
|
|
|
# Remove the relevant lines from $1
|
|
while read X; do
|
|
look "${X}|" $1
|
|
done < $1.badnodes |
|
|
comm -13 - $1 > $1.tmp
|
|
mv $1.tmp $1
|
|
|
|
rm $1.badnodes $1.nodes $2.nodes
|
|
}
|
|
|
|
# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
|
|
# with metadata not matching any entry in $1, replace the corresponding
|
|
# line of $3 with one having the same metadata as the entry in $2.
|
|
fetch_filter_modified_metadata () {
|
|
# Fish out the metadata from $1 and $2
|
|
for X in $1 $2; do
|
|
cut -f 1-6 -d '|' < ${X} > ${X}.metadata
|
|
done
|
|
|
|
# Find the metadata we need to keep
|
|
if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
|
|
comm -13 $1.metadata $2.metadata > keepmeta
|
|
else
|
|
: > keepmeta
|
|
fi
|
|
|
|
# Extract the lines which we need to remove from $3, and
|
|
# construct the lines which we need to add to $3.
|
|
: > $3.remove
|
|
: > $3.add
|
|
while read LINE; do
|
|
NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
|
|
look "${NODE}|" $3 >> $3.remove
|
|
look "${NODE}|" $3 |
|
|
cut -f 7- -d '|' |
|
|
lam -s "${LINE}|" - >> $3.add
|
|
done < keepmeta
|
|
|
|
# Remove the specified lines and add the new lines.
|
|
sort $3.remove |
|
|
comm -13 - $3 |
|
|
sort -u - $3.add > $3.tmp
|
|
mv $3.tmp $3
|
|
|
|
rm keepmeta $1.metadata $2.metadata $3.add $3.remove
|
|
}
|
|
|
|
# Remove lines from $1 and $2 which are identical;
|
|
# no need to update a file if it isn't changing.
|
|
fetch_filter_uptodate () {
|
|
comm -23 $1 $2 > $1.tmp
|
|
comm -13 $1 $2 > $2.tmp
|
|
|
|
mv $1.tmp $1
|
|
mv $2.tmp $2
|
|
}
|
|
|
|
# Fetch any "clean" old versions of files we need for merging changes.
|
|
fetch_files_premerge () {
|
|
# We only need to do anything if $1 is non-empty.
|
|
if [ -s $1 ]; then
|
|
# Tell the user what we're doing
|
|
echo -n "Fetching files from ${OLDRELNUM} for merging... "
|
|
|
|
# List of files wanted
|
|
fgrep '|f|' < $1 |
|
|
cut -f 3 -d '|' |
|
|
sort -u > files.wanted
|
|
|
|
# Only fetch the files we don't already have
|
|
while read Y; do
|
|
if [ ! -f "files/${Y}.gz" ]; then
|
|
echo ${Y};
|
|
fi
|
|
done < files.wanted > filelist
|
|
|
|
# Actually fetch them
|
|
lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist |
|
|
xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
|
|
2>${QUIETREDIR}
|
|
|
|
# Make sure we got them all, and move them into /files/
|
|
while read Y; do
|
|
if ! [ -f ${Y}.gz ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
if [ `gunzip -c < ${Y}.gz |
|
|
${SHA256} -q` = ${Y} ]; then
|
|
mv ${Y}.gz files/${Y}.gz
|
|
else
|
|
echo "${Y} has incorrect hash."
|
|
return 1
|
|
fi
|
|
done < filelist
|
|
echo "done."
|
|
|
|
# Clean up
|
|
rm filelist files.wanted
|
|
fi
|
|
}
|
|
|
|
# Prepare to fetch files: Generate a list of the files we need,
|
|
# copy the unmodified files we have into /files/, and generate
|
|
# a list of patches to download.
|
|
fetch_files_prepare () {
|
|
# Tell the user why his disk is suddenly making lots of noise
|
|
echo -n "Preparing to download files... "
|
|
|
|
# Reduce indices to ${PATH}|${HASH} pairs
|
|
for X in $1 $2 $3; do
|
|
cut -f 1,2,7 -d '|' < ${X} |
|
|
fgrep '|f|' |
|
|
cut -f 1,3 -d '|' |
|
|
sort > ${X}.hashes
|
|
done
|
|
|
|
# List of files wanted
|
|
cut -f 2 -d '|' < $3.hashes |
|
|
sort -u |
|
|
while read HASH; do
|
|
if ! [ -f files/${HASH}.gz ]; then
|
|
echo ${HASH}
|
|
fi
|
|
done > files.wanted
|
|
|
|
# Generate a list of unmodified files
|
|
comm -12 $1.hashes $2.hashes |
|
|
sort -k 1,1 -t '|' > unmodified.files
|
|
|
|
# Copy all files into /files/. We only need the unmodified files
|
|
# for use in patching; but we'll want all of them if the user asks
|
|
# to rollback the updates later.
|
|
while read LINE; do
|
|
F=`echo "${LINE}" | cut -f 1 -d '|'`
|
|
HASH=`echo "${LINE}" | cut -f 2 -d '|'`
|
|
|
|
# Skip files we already have.
|
|
if [ -f files/${HASH}.gz ]; then
|
|
continue
|
|
fi
|
|
|
|
# Make sure the file hasn't changed.
|
|
cp "${BASEDIR}/${F}" tmpfile
|
|
if [ `sha256 -q tmpfile` != ${HASH} ]; then
|
|
echo
|
|
echo "File changed while FreeBSD Update running: ${F}"
|
|
return 1
|
|
fi
|
|
|
|
# Place the file into storage.
|
|
gzip -c < tmpfile > files/${HASH}.gz
|
|
rm tmpfile
|
|
done < $2.hashes
|
|
|
|
# Produce a list of patches to download
|
|
sort -k 1,1 -t '|' $3.hashes |
|
|
join -t '|' -o 2.2,1.2 - unmodified.files |
|
|
fetch_make_patchlist > patchlist
|
|
|
|
# Garbage collect
|
|
rm unmodified.files $1.hashes $2.hashes $3.hashes
|
|
|
|
# We don't need the list of possible old files any more.
|
|
rm $1
|
|
|
|
# We're finished making noise
|
|
echo "done."
|
|
}
|
|
|
|
# Fetch files.
|
|
fetch_files () {
|
|
# Attempt to fetch patches
|
|
if [ -s patchlist ]; then
|
|
echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
|
|
echo ${NDEBUG} "patches.${DDSTATS}"
|
|
tr '|' '-' < patchlist |
|
|
lam -s "${PATCHDIR}/" - |
|
|
xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
|
|
2>${STATSREDIR} | fetch_progress
|
|
echo "done."
|
|
|
|
# Attempt to apply patches
|
|
echo -n "Applying patches... "
|
|
tr '|' ' ' < patchlist |
|
|
while read X Y; do
|
|
if [ ! -f "${X}-${Y}" ]; then continue; fi
|
|
gunzip -c < files/${X}.gz > OLD
|
|
|
|
bspatch OLD NEW ${X}-${Y}
|
|
|
|
if [ `${SHA256} -q NEW` = ${Y} ]; then
|
|
mv NEW files/${Y}
|
|
gzip -n files/${Y}
|
|
fi
|
|
rm -f diff OLD NEW ${X}-${Y}
|
|
done 2>${QUIETREDIR}
|
|
echo "done."
|
|
fi
|
|
|
|
# Download files which couldn't be generate via patching
|
|
while read Y; do
|
|
if [ ! -f "files/${Y}.gz" ]; then
|
|
echo ${Y};
|
|
fi
|
|
done < files.wanted > filelist
|
|
|
|
if [ -s filelist ]; then
|
|
echo -n "Fetching `wc -l < filelist | tr -d ' '` "
|
|
echo ${NDEBUG} "files... "
|
|
lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
|
|
xargs ${XARGST} ${PHTTPGET} ${SERVERNAME} \
|
|
2>${STATSREDIR} | fetch_progress
|
|
|
|
while read Y; do
|
|
if ! [ -f ${Y}.gz ]; then
|
|
echo "failed."
|
|
return 1
|
|
fi
|
|
if [ `gunzip -c < ${Y}.gz |
|
|
${SHA256} -q` = ${Y} ]; then
|
|
mv ${Y}.gz files/${Y}.gz
|
|
else
|
|
echo "${Y} has incorrect hash."
|
|
return 1
|
|
fi
|
|
done < filelist
|
|
echo "done."
|
|
fi
|
|
|
|
# Clean up
|
|
rm files.wanted filelist patchlist
|
|
}
|
|
|
|
# Create and populate install manifest directory; and report what updates
|
|
# are available.
|
|
fetch_create_manifest () {
|
|
# If we have an existing install manifest, nuke it.
|
|
if [ -L "${BDHASH}-install" ]; then
|
|
rm -r ${BDHASH}-install/
|
|
rm ${BDHASH}-install
|
|
fi
|
|
|
|
# Report to the user if any updates were avoided due to local changes
|
|
if [ -s modifiedfiles ]; then
|
|
cat - modifiedfiles <<- EOF | ${PAGER}
|
|
The following files are affected by updates. No changes have
|
|
been downloaded, however, because the files have been modified
|
|
locally:
|
|
EOF
|
|
fi
|
|
rm modifiedfiles
|
|
|
|
# If no files will be updated, tell the user and exit
|
|
if ! [ -s INDEX-PRESENT ] &&
|
|
! [ -s INDEX-NEW ]; then
|
|
rm INDEX-PRESENT INDEX-NEW
|
|
echo
|
|
echo -n "No updates needed to update system to "
|
|
echo "${RELNUM}-p${RELPATCHNUM}."
|
|
return
|
|
fi
|
|
|
|
# Divide files into (a) removed files, (b) added files, and
|
|
# (c) updated files.
|
|
cut -f 1 -d '|' < INDEX-PRESENT |
|
|
sort > INDEX-PRESENT.flist
|
|
cut -f 1 -d '|' < INDEX-NEW |
|
|
sort > INDEX-NEW.flist
|
|
comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
|
|
comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
|
|
comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
|
|
rm INDEX-PRESENT.flist INDEX-NEW.flist
|
|
|
|
# Report removed files, if any
|
|
if [ -s files.removed ]; then
|
|
cat - files.removed <<- EOF | ${PAGER}
|
|
The following files will be removed as part of updating to
|
|
${RELNUM}-p${RELPATCHNUM}:
|
|
EOF
|
|
fi
|
|
rm files.removed
|
|
|
|
# Report added files, if any
|
|
if [ -s files.added ]; then
|
|
cat - files.added <<- EOF | ${PAGER}
|
|
The following files will be added as part of updating to
|
|
${RELNUM}-p${RELPATCHNUM}:
|
|
EOF
|
|
fi
|
|
rm files.added
|
|
|
|
# Report updated files, if any
|
|
if [ -s files.updated ]; then
|
|
cat - files.updated <<- EOF | ${PAGER}
|
|
The following files will be updated as part of updating to
|
|
${RELNUM}-p${RELPATCHNUM}:
|
|
EOF
|
|
fi
|
|
rm files.updated
|
|
|
|
# Create a directory for the install manifest.
|
|
MDIR=`mktemp -d install.XXXXXX` || return 1
|
|
|
|
# Populate it
|
|
mv INDEX-PRESENT ${MDIR}/INDEX-OLD
|
|
mv INDEX-NEW ${MDIR}/INDEX-NEW
|
|
|
|
# Link it into place
|
|
ln -s ${MDIR} ${BDHASH}-install
|
|
}
|
|
|
|
# Warn about any upcoming EoL
|
|
fetch_warn_eol () {
|
|
# What's the current time?
|
|
NOWTIME=`date "+%s"`
|
|
|
|
# When did we last warn about the EoL date?
|
|
if [ -f lasteolwarn ]; then
|
|
LASTWARN=`cat lasteolwarn`
|
|
else
|
|
LASTWARN=`expr ${NOWTIME} - 63072000`
|
|
fi
|
|
|
|
# If the EoL time is past, warn.
|
|
if [ ${EOLTIME} -lt ${NOWTIME} ]; then
|
|
echo
|
|
cat <<-EOF
|
|
WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
|
|
Any security issues discovered after `date -r ${EOLTIME}`
|
|
will not have been corrected.
|
|
EOF
|
|
return 1
|
|
fi
|
|
|
|
# Figure out how long it has been since we last warned about the
|
|
# upcoming EoL, and how much longer we have left.
|
|
SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
|
|
TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
|
|
|
|
# Don't warn if the EoL is more than 3 months away
|
|
if [ ${TIMELEFT} -gt 7884000 ]; then
|
|
return 0
|
|
fi
|
|
|
|
# Don't warn if the time remaining is more than 3 times the time
|
|
# since the last warning.
|
|
if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
|
|
return 0
|
|
fi
|
|
|
|
# Figure out what time units to use.
|
|
if [ ${TIMELEFT} -lt 604800 ]; then
|
|
UNIT="day"
|
|
SIZE=86400
|
|
elif [ ${TIMELEFT} -lt 2678400 ]; then
|
|
UNIT="week"
|
|
SIZE=604800
|
|
else
|
|
UNIT="month"
|
|
SIZE=2678400
|
|
fi
|
|
|
|
# Compute the right number of units
|
|
NUM=`expr ${TIMELEFT} / ${SIZE}`
|
|
if [ ${NUM} != 1 ]; then
|
|
UNIT="${UNIT}s"
|
|
fi
|
|
|
|
# Print the warning
|
|
echo
|
|
cat <<-EOF
|
|
WARNING: `uname -sr` is approaching its End-of-Life date.
|
|
It is strongly recommended that you upgrade to a newer
|
|
release within the next ${NUM} ${UNIT}.
|
|
EOF
|
|
|
|
# Update the stored time of last warning
|
|
echo ${NOWTIME} > lasteolwarn
|
|
}
|
|
|
|
# Do the actual work involved in "fetch" / "cron".
|
|
fetch_run () {
|
|
workdir_init || return 1
|
|
|
|
# Prepare the mirror list.
|
|
fetch_pick_server_init && fetch_pick_server
|
|
|
|
# Try to fetch the public key until we run out of servers.
|
|
while ! fetch_key; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
|
|
# Try to fetch the metadata index signature ("tag") until we run
|
|
# out of available servers; and sanity check the downloaded tag.
|
|
while ! fetch_tag; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
fetch_tagsanity || return 1
|
|
|
|
# Fetch the latest INDEX-NEW and INDEX-OLD files.
|
|
fetch_metadata INDEX-NEW INDEX-OLD || return 1
|
|
|
|
# Generate filtered INDEX-NEW and INDEX-OLD files containing only
|
|
# the lines which (a) belong to components we care about, and (b)
|
|
# don't correspond to paths we're explicitly ignoring.
|
|
fetch_filter_metadata INDEX-NEW || return 1
|
|
fetch_filter_metadata INDEX-OLD || return 1
|
|
|
|
# Translate /boot/${KERNCONF} into ${KERNELDIR}
|
|
fetch_filter_kernel_names INDEX-NEW ${KERNCONF}
|
|
fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
|
|
|
|
# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
|
|
# system and generate an INDEX-PRESENT file.
|
|
fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
|
|
|
|
# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
|
|
# correspond to lines in INDEX-PRESENT with hashes not appearing
|
|
# in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in
|
|
# INDEX-PRESENT has type - and there isn't a corresponding entry in
|
|
# INDEX-OLD with type -.
|
|
fetch_filter_unmodified_notpresent \
|
|
INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null
|
|
|
|
# For each entry in INDEX-PRESENT of type -, remove any corresponding
|
|
# entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries
|
|
# of type - from INDEX-PRESENT.
|
|
fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
|
|
|
|
# If ${ALLOWDELETE} != "yes", then remove any entries from
|
|
# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
|
|
fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
|
|
|
|
# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
|
|
# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
|
|
# replace the corresponding line of INDEX-NEW with one having the
|
|
# same metadata as the entry in INDEX-PRESENT.
|
|
fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
|
|
|
|
# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
|
|
# no need to update a file if it isn't changing.
|
|
fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
|
|
|
|
# Prepare to fetch files: Generate a list of the files we need,
|
|
# copy the unmodified files we have into /files/, and generate
|
|
# a list of patches to download.
|
|
fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
|
|
|
|
# Fetch files.
|
|
fetch_files || return 1
|
|
|
|
# Create and populate install manifest directory; and report what
|
|
# updates are available.
|
|
fetch_create_manifest || return 1
|
|
|
|
# Warn about any upcoming EoL
|
|
fetch_warn_eol || return 1
|
|
}
|
|
|
|
# If StrictComponents is not "yes", generate a new components list
|
|
# with only the components which appear to be installed.
|
|
upgrade_guess_components () {
|
|
if [ "${STRICTCOMPONENTS}" = "no" ]; then
|
|
# Generate filtered INDEX-ALL with only the components listed
|
|
# in COMPONENTS.
|
|
fetch_filter_metadata_components $1 || return 1
|
|
|
|
# Tell the user why his disk is suddenly making lots of noise
|
|
echo -n "Inspecting system... "
|
|
|
|
# Look at the files on disk, and assume that a component is
|
|
# supposed to be present if it is more than half-present.
|
|
cut -f 1-3 -d '|' < INDEX-ALL |
|
|
tr '|' ' ' |
|
|
while read C S F; do
|
|
if [ -e ${BASEDIR}/${F} ]; then
|
|
echo "+ ${C}|${S}"
|
|
fi
|
|
echo "= ${C}|${S}"
|
|
done |
|
|
sort |
|
|
uniq -c |
|
|
sed -E 's,^ +,,' > compfreq
|
|
grep ' = ' compfreq |
|
|
cut -f 1,3 -d ' ' |
|
|
sort -k 2,2 -t ' ' > compfreq.total
|
|
grep ' + ' compfreq |
|
|
cut -f 1,3 -d ' ' |
|
|
sort -k 2,2 -t ' ' > compfreq.present
|
|
join -t ' ' -1 2 -2 2 compfreq.present compfreq.total |
|
|
while read S P T; do
|
|
if [ ${T} -ne 0 -a ${P} -gt `expr ${T} / 2` ]; then
|
|
echo ${S}
|
|
fi
|
|
done > comp.present
|
|
cut -f 2 -d ' ' < compfreq.total > comp.total
|
|
rm INDEX-ALL compfreq compfreq.total compfreq.present
|
|
|
|
# We're done making noise.
|
|
echo "done."
|
|
|
|
# Sometimes the kernel isn't installed where INDEX-ALL
|
|
# thinks that it should be: In particular, it is often in
|
|
# /boot/kernel instead of /boot/GENERIC or /boot/SMP. To
|
|
# deal with this, if "kernel|X" is listed in comp.total
|
|
# (i.e., is a component which would be upgraded if it is
|
|
# found to be present) we will add it to comp.present.
|
|
# If "kernel|<anything>" is in comp.total but "kernel|X" is
|
|
# not, we print a warning -- the user is running a kernel
|
|
# which isn't part of the release.
|
|
KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'`
|
|
grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present
|
|
|
|
if grep -qE "^kernel\|" comp.total &&
|
|
! grep -qE "^kernel\|${KCOMP}\$" comp.total; then
|
|
cat <<-EOF
|
|
|
|
WARNING: This system is running a "${KCOMP}" kernel, which is not a
|
|
kernel configuration distributed as part of FreeBSD ${RELNUM}.
|
|
This kernel will not be updated: you MUST update the kernel manually
|
|
before running "$0 install".
|
|
EOF
|
|
fi
|
|
|
|
# Re-sort the list of installed components and generate
|
|
# the list of non-installed components.
|
|
sort -u < comp.present > comp.present.tmp
|
|
mv comp.present.tmp comp.present
|
|
comm -13 comp.present comp.total > comp.absent
|
|
|
|
# Ask the user to confirm that what we have is correct. To
|
|
# reduce user confusion, translate "X|Y" back to "X/Y" (as
|
|
# subcomponents must be listed in the configuration file).
|
|
echo
|
|
echo -n "The following components of FreeBSD "
|
|
echo "seem to be installed:"
|
|
tr '|' '/' < comp.present |
|
|
fmt -72
|
|
echo
|
|
echo -n "The following components of FreeBSD "
|
|
echo "do not seem to be installed:"
|
|
tr '|' '/' < comp.absent |
|
|
fmt -72
|
|
echo
|
|
continuep || return 1
|
|
echo
|
|
|
|
# Suck the generated list of components into ${COMPONENTS}.
|
|
# Note that comp.present.tmp is used due to issues with
|
|
# pipelines and setting variables.
|
|
COMPONENTS=""
|
|
tr '|' '/' < comp.present > comp.present.tmp
|
|
while read C; do
|
|
COMPONENTS="${COMPONENTS} ${C}"
|
|
done < comp.present.tmp
|
|
|
|
# Delete temporary files
|
|
rm comp.present comp.present.tmp comp.absent comp.total
|
|
fi
|
|
}
|
|
|
|
# If StrictComponents is not "yes", COMPONENTS contains an entry
|
|
# corresponding to the currently running kernel, and said kernel
|
|
# does not exist in the new release, add "kernel/generic" to the
|
|
# list of components.
|
|
upgrade_guess_new_kernel () {
|
|
if [ "${STRICTCOMPONENTS}" = "no" ]; then
|
|
# Grab the unfiltered metadata file.
|
|
METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
|
|
gunzip -c < files/${METAHASH}.gz > $1.all
|
|
|
|
# If "kernel/${KCOMP}" is in ${COMPONENTS} and that component
|
|
# isn't in $1.all, we need to add kernel/generic.
|
|
for C in ${COMPONENTS}; do
|
|
if [ ${C} = "kernel/${KCOMP}" ] &&
|
|
! grep -qE "^kernel\|${KCOMP}\|" $1.all; then
|
|
COMPONENTS="${COMPONENTS} kernel/generic"
|
|
NKERNCONF="GENERIC"
|
|
cat <<-EOF
|
|
|
|
WARNING: This system is running a "${KCOMP}" kernel, which is not a
|
|
kernel configuration distributed as part of FreeBSD ${RELNUM}.
|
|
As part of upgrading to FreeBSD ${RELNUM}, this kernel will be
|
|
replaced with a "generic" kernel.
|
|
EOF
|
|
continuep || return 1
|
|
fi
|
|
done
|
|
|
|
# Don't need this any more...
|
|
rm $1.all
|
|
fi
|
|
}
|
|
|
|
# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
|
|
# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
|
|
upgrade_oldall_to_oldnew () {
|
|
# For each ${F}|... which appears in INDEX-ALL but does not appear
|
|
# in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD.
|
|
cut -f 1 -d '|' < $1 |
|
|
sort -u > $1.paths
|
|
cut -f 1 -d '|' < $2 |
|
|
sort -u |
|
|
comm -13 $1.paths - |
|
|
lam - -s "|-||||||" |
|
|
sort - $1 > $1.tmp
|
|
mv $1.tmp $1
|
|
|
|
# Remove lines from INDEX-OLD which also appear in INDEX-ALL
|
|
comm -23 $1 $2 > $1.tmp
|
|
mv $1.tmp $1
|
|
|
|
# Remove lines from INDEX-ALL which have a file name not appearing
|
|
# anywhere in INDEX-OLD (since these must be files which haven't
|
|
# changed -- if they were new, there would be an entry of type "-").
|
|
cut -f 1 -d '|' < $1 |
|
|
sort -u > $1.paths
|
|
sort -k 1,1 -t '|' < $2 |
|
|
join -t '|' - $1.paths |
|
|
sort > $2.tmp
|
|
rm $1.paths
|
|
mv $2.tmp $2
|
|
|
|
# Rename INDEX-ALL to INDEX-NEW.
|
|
mv $2 $3
|
|
}
|
|
|
|
# Helper for upgrade_merge: Return zero true iff the two files differ only
|
|
# in the contents of their RCS tags.
|
|
samef () {
|
|
X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}`
|
|
Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}`
|
|
|
|
if [ $X = $Y ]; then
|
|
return 0;
|
|
else
|
|
return 1;
|
|
fi
|
|
}
|
|
|
|
# From the list of "old" files in $1, merge changes in $2 with those in $3,
|
|
# and update $3 to reflect the hashes of merged files.
|
|
upgrade_merge () {
|
|
# We only need to do anything if $1 is non-empty.
|
|
if [ -s $1 ]; then
|
|
cut -f 1 -d '|' $1 |
|
|
sort > $1-paths
|
|
|
|
# Create staging area for merging files
|
|
rm -rf merge/
|
|
while read F; do
|
|
D=`dirname ${F}`
|
|
mkdir -p merge/old/${D}
|
|
mkdir -p merge/${OLDRELNUM}/${D}
|
|
mkdir -p merge/${RELNUM}/${D}
|
|
mkdir -p merge/new/${D}
|
|
done < $1-paths
|
|
|
|
# Copy in files
|
|
while read F; do
|
|
# Currently installed file
|
|
V=`look "${F}|" $2 | cut -f 7 -d '|'`
|
|
gunzip < files/${V}.gz > merge/old/${F}
|
|
|
|
# Old release
|
|
if look "${F}|" $1 | fgrep -q "|f|"; then
|
|
V=`look "${F}|" $1 | cut -f 3 -d '|'`
|
|
gunzip < files/${V}.gz \
|
|
> merge/${OLDRELNUM}/${F}
|
|
fi
|
|
|
|
# New release
|
|
if look "${F}|" $3 | cut -f 1,2,7 -d '|' |
|
|
fgrep -q "|f|"; then
|
|
V=`look "${F}|" $3 | cut -f 7 -d '|'`
|
|
gunzip < files/${V}.gz \
|
|
> merge/${RELNUM}/${F}
|
|
fi
|
|
done < $1-paths
|
|
|
|
# Attempt to automatically merge changes
|
|
echo -n "Attempting to automatically merge "
|
|
echo -n "changes in files..."
|
|
: > failed.merges
|
|
while read F; do
|
|
# If the file doesn't exist in the new release,
|
|
# the result of "merging changes" is having the file
|
|
# not exist.
|
|
if ! [ -f merge/${RELNUM}/${F} ]; then
|
|
continue
|
|
fi
|
|
|
|
# If the file didn't exist in the old release, we're
|
|
# going to throw away the existing file and hope that
|
|
# the version from the new release is what we want.
|
|
if ! [ -f merge/${OLDRELNUM}/${F} ]; then
|
|
cp merge/${RELNUM}/${F} merge/new/${F}
|
|
continue
|
|
fi
|
|
|
|
# Some files need special treatment.
|
|
case ${F} in
|
|
/etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
|
|
# Don't merge these -- we're rebuild them
|
|
# after updates are installed.
|
|
cp merge/old/${F} merge/new/${F}
|
|
;;
|
|
*)
|
|
if ! diff3 -E -m -L "current version" \
|
|
-L "${OLDRELNUM}" -L "${RELNUM}" \
|
|
merge/old/${F} \
|
|
merge/${OLDRELNUM}/${F} \
|
|
merge/${RELNUM}/${F} \
|
|
> merge/new/${F} 2>/dev/null; then
|
|
echo ${F} >> failed.merges
|
|
fi
|
|
;;
|
|
esac
|
|
done < $1-paths
|
|
echo " done."
|
|
|
|
# Ask the user to handle any files which didn't merge.
|
|
while read F; do
|
|
# If the installed file differs from the version in
|
|
# the old release only due to RCS tag expansion
|
|
# then just use the version in the new release.
|
|
if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
|
|
cp merge/${RELNUM}/${F} merge/new/${F}
|
|
continue
|
|
fi
|
|
|
|
cat <<-EOF
|
|
|
|
The following file could not be merged automatically: ${F}
|
|
Press Enter to edit this file in ${EDITOR} and resolve the conflicts
|
|
manually...
|
|
EOF
|
|
while true; do
|
|
read dummy </dev/tty
|
|
${EDITOR} `pwd`/merge/new/${F} < /dev/tty
|
|
|
|
if ! grep -qE '^(<<<<<<<|=======|>>>>>>>)([[:space:]].*)?$' $(pwd)/merge/new/${F} ; then
|
|
break
|
|
fi
|
|
cat <<-EOF
|
|
|
|
Merge conflict markers remain in: ${F}
|
|
These must be resolved for the system to be functional.
|
|
|
|
Press Enter to return to editing this file.
|
|
EOF
|
|
done
|
|
done < failed.merges
|
|
rm failed.merges
|
|
|
|
# Ask the user to confirm that he likes how the result
|
|
# of merging files.
|
|
while read F; do
|
|
# Skip files which haven't changed except possibly
|
|
# in their RCS tags.
|
|
if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
|
|
samef merge/old/${F} merge/new/${F}; then
|
|
continue
|
|
fi
|
|
|
|
# Skip files where the installed file differs from
|
|
# the old file only due to RCS tags.
|
|
if [ -f merge/old/${F} ] &&
|
|
[ -f merge/${OLDRELNUM}/${F} ] &&
|
|
samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
|
|
continue
|
|
fi
|
|
|
|
# Warn about files which are ceasing to exist.
|
|
if ! [ -f merge/new/${F} ]; then
|
|
cat <<-EOF
|
|
|
|
The following file will be removed, as it no longer exists in
|
|
FreeBSD ${RELNUM}: ${F}
|
|
EOF
|
|
continuep < /dev/tty || return 1
|
|
continue
|
|
fi
|
|
|
|
# Print changes for the user's approval.
|
|
cat <<-EOF
|
|
|
|
The following changes, which occurred between FreeBSD ${OLDRELNUM} and
|
|
FreeBSD ${RELNUM} have been merged into ${F}:
|
|
EOF
|
|
diff -U 5 -L "current version" -L "new version" \
|
|
merge/old/${F} merge/new/${F} || true
|
|
continuep < /dev/tty || return 1
|
|
done < $1-paths
|
|
|
|
# Store merged files.
|
|
while read F; do
|
|
if [ -f merge/new/${F} ]; then
|
|
V=`${SHA256} -q merge/new/${F}`
|
|
|
|
gzip -c < merge/new/${F} > files/${V}.gz
|
|
echo "${F}|${V}"
|
|
fi
|
|
done < $1-paths > newhashes
|
|
|
|
# Pull lines out from $3 which need to be updated to
|
|
# reflect merged files.
|
|
while read F; do
|
|
look "${F}|" $3
|
|
done < $1-paths > $3-oldlines
|
|
|
|
# Update lines to reflect merged files
|
|
join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8 \
|
|
$3-oldlines newhashes > $3-newlines
|
|
|
|
# Remove old lines from $3 and add new lines.
|
|
sort $3-oldlines |
|
|
comm -13 - $3 |
|
|
sort - $3-newlines > $3.tmp
|
|
mv $3.tmp $3
|
|
|
|
# Clean up
|
|
rm $1-paths newhashes $3-oldlines $3-newlines
|
|
rm -rf merge/
|
|
fi
|
|
|
|
# We're done with merging files.
|
|
rm $1
|
|
}
|
|
|
|
# Do the work involved in fetching upgrades to a new release
|
|
upgrade_run () {
|
|
workdir_init || return 1
|
|
|
|
# Prepare the mirror list.
|
|
fetch_pick_server_init && fetch_pick_server
|
|
|
|
# Try to fetch the public key until we run out of servers.
|
|
while ! fetch_key; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
|
|
# Try to fetch the metadata index signature ("tag") until we run
|
|
# out of available servers; and sanity check the downloaded tag.
|
|
while ! fetch_tag; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
fetch_tagsanity || return 1
|
|
|
|
# Fetch the INDEX-OLD and INDEX-ALL.
|
|
fetch_metadata INDEX-OLD INDEX-ALL || return 1
|
|
|
|
# If StrictComponents is not "yes", generate a new components list
|
|
# with only the components which appear to be installed.
|
|
upgrade_guess_components INDEX-ALL || return 1
|
|
|
|
# Generate filtered INDEX-OLD and INDEX-ALL files containing only
|
|
# the components we want and without anything marked as "Ignore".
|
|
fetch_filter_metadata INDEX-OLD || return 1
|
|
fetch_filter_metadata INDEX-ALL || return 1
|
|
|
|
# Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
|
|
sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
|
|
mv INDEX-OLD.tmp INDEX-OLD
|
|
rm INDEX-ALL
|
|
|
|
# Adjust variables for fetching files from the new release.
|
|
OLDRELNUM=${RELNUM}
|
|
RELNUM=${TARGETRELEASE}
|
|
OLDFETCHDIR=${FETCHDIR}
|
|
FETCHDIR=${RELNUM}/${ARCH}
|
|
|
|
# Try to fetch the NEW metadata index signature ("tag") until we run
|
|
# out of available servers; and sanity check the downloaded tag.
|
|
while ! fetch_tag; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
|
|
# Fetch the new INDEX-ALL.
|
|
fetch_metadata INDEX-ALL || return 1
|
|
|
|
# If StrictComponents is not "yes", COMPONENTS contains an entry
|
|
# corresponding to the currently running kernel, and said kernel
|
|
# does not exist in the new release, add "kernel/generic" to the
|
|
# list of components.
|
|
upgrade_guess_new_kernel INDEX-ALL || return 1
|
|
|
|
# Filter INDEX-ALL to contain only the components we want and without
|
|
# anything marked as "Ignore".
|
|
fetch_filter_metadata INDEX-ALL || return 1
|
|
|
|
# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
|
|
# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
|
|
upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
|
|
|
|
# Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
|
|
fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
|
|
fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
|
|
|
|
# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
|
|
# system and generate an INDEX-PRESENT file.
|
|
fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
|
|
|
|
# Based on ${MERGECHANGES}, generate a file tomerge-old with the
|
|
# paths and hashes of old versions of files to merge.
|
|
fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
|
|
|
|
# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
|
|
# correspond to lines in INDEX-PRESENT with hashes not appearing
|
|
# in INDEX-OLD or INDEX-NEW. Also remove lines where the entry in
|
|
# INDEX-PRESENT has type - and there isn't a corresponding entry in
|
|
# INDEX-OLD with type -.
|
|
fetch_filter_unmodified_notpresent \
|
|
INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
|
|
|
|
# For each entry in INDEX-PRESENT of type -, remove any corresponding
|
|
# entry from INDEX-NEW if ${ALLOWADD} != "yes". Remove all entries
|
|
# of type - from INDEX-PRESENT.
|
|
fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
|
|
|
|
# If ${ALLOWDELETE} != "yes", then remove any entries from
|
|
# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
|
|
fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
|
|
|
|
# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
|
|
# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
|
|
# replace the corresponding line of INDEX-NEW with one having the
|
|
# same metadata as the entry in INDEX-PRESENT.
|
|
fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
|
|
|
|
# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
|
|
# no need to update a file if it isn't changing.
|
|
fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
|
|
|
|
# Fetch "clean" files from the old release for merging changes.
|
|
fetch_files_premerge tomerge-old
|
|
|
|
# Prepare to fetch files: Generate a list of the files we need,
|
|
# copy the unmodified files we have into /files/, and generate
|
|
# a list of patches to download.
|
|
fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
|
|
|
|
# Fetch patches from to-${RELNUM}/${ARCH}/bp/
|
|
PATCHDIR=to-${RELNUM}/${ARCH}/bp
|
|
fetch_files || return 1
|
|
|
|
# Merge configuration file changes.
|
|
upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
|
|
|
|
# Create and populate install manifest directory; and report what
|
|
# updates are available.
|
|
fetch_create_manifest || return 1
|
|
|
|
# Leave a note behind to tell the "install" command that the kernel
|
|
# needs to be installed before the world.
|
|
touch ${BDHASH}-install/kernelfirst
|
|
|
|
# Remind the user that they need to run "freebsd-update install"
|
|
# to install the downloaded bits, in case they didn't RTFM.
|
|
echo "To install the downloaded upgrades, run \"$0 install\"."
|
|
}
|
|
|
|
# Make sure that all the file hashes mentioned in $@ have corresponding
|
|
# gzipped files stored in /files/.
|
|
install_verify () {
|
|
# Generate a list of hashes
|
|
cat $@ |
|
|
cut -f 2,7 -d '|' |
|
|
grep -E '^f' |
|
|
cut -f 2 -d '|' |
|
|
sort -u > filelist
|
|
|
|
# Make sure all the hashes exist
|
|
while read HASH; do
|
|
if ! [ -f files/${HASH}.gz ]; then
|
|
echo -n "Update files missing -- "
|
|
echo "this should never happen."
|
|
echo "Re-run '$0 fetch'."
|
|
return 1
|
|
fi
|
|
done < filelist
|
|
|
|
# Clean up
|
|
rm filelist
|
|
}
|
|
|
|
# Remove the system immutable flag from files
|
|
install_unschg () {
|
|
# Generate file list
|
|
cat $@ |
|
|
cut -f 1 -d '|' > filelist
|
|
|
|
# Remove flags
|
|
while read F; do
|
|
if ! [ -e ${BASEDIR}/${F} ]; then
|
|
continue
|
|
else
|
|
echo ${BASEDIR}/${F}
|
|
fi
|
|
done < filelist | xargs chflags noschg || return 1
|
|
|
|
# Clean up
|
|
rm filelist
|
|
}
|
|
|
|
# Decide which directory name to use for kernel backups.
|
|
backup_kernel_finddir () {
|
|
CNT=0
|
|
while true ; do
|
|
# Pathname does not exist, so it is OK use that name
|
|
# for backup directory.
|
|
if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
|
|
return 0
|
|
fi
|
|
|
|
# If directory do exist, we only use if it has our
|
|
# marker file.
|
|
if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
|
|
-e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
|
|
return 0
|
|
fi
|
|
|
|
# We could not use current directory name, so add counter to
|
|
# the end and try again.
|
|
CNT=$((CNT + 1))
|
|
if [ $CNT -gt 9 ]; then
|
|
echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
|
|
exit 1
|
|
fi
|
|
BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
|
|
BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
|
|
done
|
|
}
|
|
|
|
# Backup the current kernel using hardlinks, if not disabled by user.
|
|
# Since we delete all files in the directory used for previous backups
|
|
# we create a marker file called ".freebsd-update" in the directory so
|
|
# we can determine on the next run that the directory was created by
|
|
# freebsd-update and we then do not accidentally remove user files in
|
|
# the unlikely case that the user has created a directory with a
|
|
# conflicting name.
|
|
backup_kernel () {
|
|
# Only make kernel backup is so configured.
|
|
if [ $BACKUPKERNEL != yes ]; then
|
|
return 0
|
|
fi
|
|
|
|
# Decide which directory name to use for kernel backups.
|
|
backup_kernel_finddir
|
|
|
|
# Remove old kernel backup files. If $BACKUPKERNELDIR was
|
|
# "not ours", backup_kernel_finddir would have exited, so
|
|
# deleting the directory content is as safe as we can make it.
|
|
if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
|
|
rm -fr $BASEDIR/$BACKUPKERNELDIR
|
|
fi
|
|
|
|
# Create directories for backup.
|
|
mkdir -p $BASEDIR/$BACKUPKERNELDIR
|
|
mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
|
|
mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
|
|
|
|
# Mark the directory as having been created by freebsd-update.
|
|
touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
|
|
if [ $? -ne 0 ]; then
|
|
echo "Could not create kernel backup directory"
|
|
exit 1
|
|
fi
|
|
|
|
# Disable pathname expansion to be sure *.symbols is not
|
|
# expanded.
|
|
set -f
|
|
|
|
# Use find to ignore symbol files, unless disabled by user.
|
|
if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
|
|
FINDFILTER=""
|
|
else
|
|
FINDFILTER="-a ! -name *.debug -a ! -name *.symbols"
|
|
fi
|
|
|
|
# Backup all the kernel files using hardlinks.
|
|
(cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
|
|
cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
|
|
|
|
# Re-enable patchname expansion.
|
|
set +f
|
|
}
|
|
|
|
# Install new files
|
|
install_from_index () {
|
|
# First pass: Do everything apart from setting file flags. We
|
|
# can't set flags yet, because schg inhibits hard linking.
|
|
sort -k 1,1 -t '|' $1 |
|
|
tr '|' ' ' |
|
|
while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
|
|
case ${TYPE} in
|
|
d)
|
|
# Create a directory
|
|
install -d -o ${OWNER} -g ${GROUP} \
|
|
-m ${PERM} ${BASEDIR}/${FPATH}
|
|
;;
|
|
f)
|
|
if [ -z "${LINK}" ]; then
|
|
# Create a file, without setting flags.
|
|
gunzip < files/${HASH}.gz > ${HASH}
|
|
install -S -o ${OWNER} -g ${GROUP} \
|
|
-m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
|
|
rm ${HASH}
|
|
else
|
|
# Create a hard link.
|
|
ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
|
|
fi
|
|
;;
|
|
L)
|
|
# Create a symlink
|
|
ln -sfh ${HASH} ${BASEDIR}/${FPATH}
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Perform a second pass, adding file flags.
|
|
tr '|' ' ' < $1 |
|
|
while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
|
|
if [ ${TYPE} = "f" ] &&
|
|
! [ ${FLAGS} = "0" ]; then
|
|
chflags ${FLAGS} ${BASEDIR}/${FPATH}
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Remove files which we want to delete
|
|
install_delete () {
|
|
# Generate list of new files
|
|
cut -f 1 -d '|' < $2 |
|
|
sort > newfiles
|
|
|
|
# Generate subindex of old files we want to nuke
|
|
sort -k 1,1 -t '|' $1 |
|
|
join -t '|' -v 1 - newfiles |
|
|
sort -r -k 1,1 -t '|' |
|
|
cut -f 1,2 -d '|' |
|
|
tr '|' ' ' > killfiles
|
|
|
|
# Remove the offending bits
|
|
while read FPATH TYPE; do
|
|
case ${TYPE} in
|
|
d)
|
|
rmdir ${BASEDIR}/${FPATH}
|
|
;;
|
|
f)
|
|
rm ${BASEDIR}/${FPATH}
|
|
;;
|
|
L)
|
|
rm ${BASEDIR}/${FPATH}
|
|
;;
|
|
esac
|
|
done < killfiles
|
|
|
|
# Clean up
|
|
rm newfiles killfiles
|
|
}
|
|
|
|
# Install new files, delete old files, and update generated files
|
|
install_files () {
|
|
# If we haven't already dealt with the kernel, deal with it.
|
|
if ! [ -f $1/kerneldone ]; then
|
|
grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
|
|
grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
|
|
|
|
# Backup current kernel before installing a new one
|
|
backup_kernel || return 1
|
|
|
|
# Install new files
|
|
install_from_index INDEX-NEW || return 1
|
|
|
|
# Remove files which need to be deleted
|
|
install_delete INDEX-OLD INDEX-NEW || return 1
|
|
|
|
# Update linker.hints if necessary
|
|
if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
|
|
kldxref -R ${BASEDIR}/boot/ 2>/dev/null
|
|
fi
|
|
|
|
# We've finished updating the kernel.
|
|
touch $1/kerneldone
|
|
|
|
# Do we need to ask for a reboot now?
|
|
if [ -f $1/kernelfirst ] &&
|
|
[ -s INDEX-OLD -o -s INDEX-NEW ]; then
|
|
cat <<-EOF
|
|
|
|
Kernel updates have been installed. Please reboot and run
|
|
"$0 install" again to finish installing updates.
|
|
EOF
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# If we haven't already dealt with the world, deal with it.
|
|
if ! [ -f $1/worlddone ]; then
|
|
# Create any necessary directories first
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -E '^[^|]+\|d\|' > INDEX-NEW
|
|
install_from_index INDEX-NEW || return 1
|
|
|
|
# Install new runtime linker
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW
|
|
install_from_index INDEX-NEW || return 1
|
|
|
|
# Install new shared libraries next
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
|
|
grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
|
|
install_from_index INDEX-NEW || return 1
|
|
|
|
# Deal with everything else
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
|
|
grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
|
|
grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
|
|
install_from_index INDEX-NEW || return 1
|
|
install_delete INDEX-OLD INDEX-NEW || return 1
|
|
|
|
# Restart sshd if running (PR263489). Note that this does not
|
|
# affect child sshd processes handling existing sessions.
|
|
if service sshd status >/dev/null 2>/dev/null; then
|
|
echo
|
|
echo "Restarting sshd after upgrade"
|
|
service sshd restart
|
|
fi
|
|
|
|
# Rehash certs if we actually have certctl installed.
|
|
if which certctl>/dev/null; then
|
|
env DESTDIR=${BASEDIR} certctl rehash
|
|
fi
|
|
|
|
# Rebuild generated pwd files and /etc/login.conf.db.
|
|
pwd_mkdb -d ${BASEDIR}/etc -p ${BASEDIR}/etc/master.passwd
|
|
cap_mkdb ${BASEDIR}/etc/login.conf
|
|
|
|
# Rebuild man page databases, if necessary.
|
|
for D in /usr/share/man /usr/share/openssl/man; do
|
|
if [ ! -d ${BASEDIR}/$D ]; then
|
|
continue
|
|
fi
|
|
if [ -f ${BASEDIR}/$D/mandoc.db ] && \
|
|
[ -z "$(find ${BASEDIR}/$D -type f -newer ${BASEDIR}/$D/mandoc.db)" ]; then
|
|
continue;
|
|
fi
|
|
makewhatis ${BASEDIR}/$D
|
|
done
|
|
|
|
# We've finished installing the world and deleting old files
|
|
# which are not shared libraries.
|
|
touch $1/worlddone
|
|
|
|
# Do we need to ask the user to portupgrade now?
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
|
|
cut -f 1 -d '|' |
|
|
sort > newfiles
|
|
if grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
|
|
cut -f 1 -d '|' |
|
|
sort |
|
|
join -v 1 - newfiles |
|
|
grep -q .; then
|
|
cat <<-EOF
|
|
|
|
Completing this upgrade requires removing old shared object files.
|
|
Please rebuild all installed 3rd party software (e.g., programs
|
|
installed from the ports tree) and then run "$0 install"
|
|
again to finish installing updates.
|
|
EOF
|
|
rm newfiles
|
|
exit 0
|
|
fi
|
|
rm newfiles
|
|
fi
|
|
|
|
# Remove old shared libraries
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -vE '^[^|]+\|d\|' |
|
|
grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
|
|
install_delete INDEX-OLD INDEX-NEW || return 1
|
|
|
|
# Remove old directories
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -E '^[^|]+\|d\|' > INDEX-NEW
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -E '^[^|]+\|d\|' > INDEX-OLD
|
|
install_delete INDEX-OLD INDEX-NEW || return 1
|
|
|
|
# Remove temporary files
|
|
rm INDEX-OLD INDEX-NEW
|
|
}
|
|
|
|
# Rearrange bits to allow the installed updates to be rolled back
|
|
install_setup_rollback () {
|
|
# Remove the "reboot after installing kernel", "kernel updated", and
|
|
# "finished installing the world" flags if present -- they are
|
|
# irrelevant when rolling back updates.
|
|
if [ -f ${BDHASH}-install/kernelfirst ]; then
|
|
rm ${BDHASH}-install/kernelfirst
|
|
rm ${BDHASH}-install/kerneldone
|
|
fi
|
|
if [ -f ${BDHASH}-install/worlddone ]; then
|
|
rm ${BDHASH}-install/worlddone
|
|
fi
|
|
|
|
if [ -L ${BDHASH}-rollback ]; then
|
|
mv ${BDHASH}-rollback ${BDHASH}-install/rollback
|
|
fi
|
|
|
|
mv ${BDHASH}-install ${BDHASH}-rollback
|
|
}
|
|
|
|
# Actually install updates
|
|
install_run () {
|
|
echo -n "Installing updates..."
|
|
|
|
# Make sure we have all the files we should have
|
|
install_verify ${BDHASH}-install/INDEX-OLD \
|
|
${BDHASH}-install/INDEX-NEW || return 1
|
|
|
|
# Remove system immutable flag from files
|
|
install_unschg ${BDHASH}-install/INDEX-OLD \
|
|
${BDHASH}-install/INDEX-NEW || return 1
|
|
|
|
# Install new files, delete old files, and update linker.hints
|
|
install_files ${BDHASH}-install || return 1
|
|
|
|
# Rearrange bits to allow the installed updates to be rolled back
|
|
install_setup_rollback
|
|
|
|
echo " done."
|
|
}
|
|
|
|
# Rearrange bits to allow the previous set of updates to be rolled back next.
|
|
rollback_setup_rollback () {
|
|
if [ -L ${BDHASH}-rollback/rollback ]; then
|
|
mv ${BDHASH}-rollback/rollback rollback-tmp
|
|
rm -r ${BDHASH}-rollback/
|
|
rm ${BDHASH}-rollback
|
|
mv rollback-tmp ${BDHASH}-rollback
|
|
else
|
|
rm -r ${BDHASH}-rollback/
|
|
rm ${BDHASH}-rollback
|
|
fi
|
|
}
|
|
|
|
# Install old files, delete new files, and update linker.hints
|
|
rollback_files () {
|
|
# Install old shared library files which don't have the same path as
|
|
# a new shared library file.
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -E '/lib/.*\.so\.[0-9]+\|' |
|
|
cut -f 1 -d '|' |
|
|
sort > INDEX-NEW.libs.flist
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -E '/lib/.*\.so\.[0-9]+\|' |
|
|
sort -k 1,1 -t '|' - |
|
|
join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
|
|
install_from_index INDEX-OLD || return 1
|
|
|
|
# Deal with files which are neither kernel nor shared library
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
|
|
install_from_index INDEX-OLD || return 1
|
|
install_delete INDEX-NEW INDEX-OLD || return 1
|
|
|
|
# Install any old shared library files which we didn't install above.
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -E '/lib/.*\.so\.[0-9]+\|' |
|
|
sort -k 1,1 -t '|' - |
|
|
join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
|
|
install_from_index INDEX-OLD || return 1
|
|
|
|
# Delete unneeded shared library files
|
|
grep -vE '^/boot/' $1/INDEX-OLD |
|
|
grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
|
|
grep -vE '^/boot/' $1/INDEX-NEW |
|
|
grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
|
|
install_delete INDEX-NEW INDEX-OLD || return 1
|
|
|
|
# Deal with kernel files
|
|
grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
|
|
grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
|
|
install_from_index INDEX-OLD || return 1
|
|
install_delete INDEX-NEW INDEX-OLD || return 1
|
|
if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
|
|
kldxref -R /boot/ 2>/dev/null
|
|
fi
|
|
|
|
# Remove temporary files
|
|
rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
|
|
}
|
|
|
|
# Actually rollback updates
|
|
rollback_run () {
|
|
echo -n "Uninstalling updates..."
|
|
|
|
# If there are updates waiting to be installed, remove them; we
|
|
# want the user to re-run 'fetch' after rolling back updates.
|
|
if [ -L ${BDHASH}-install ]; then
|
|
rm -r ${BDHASH}-install/
|
|
rm ${BDHASH}-install
|
|
fi
|
|
|
|
# Make sure we have all the files we should have
|
|
install_verify ${BDHASH}-rollback/INDEX-NEW \
|
|
${BDHASH}-rollback/INDEX-OLD || return 1
|
|
|
|
# Remove system immutable flag from files
|
|
install_unschg ${BDHASH}-rollback/INDEX-NEW \
|
|
${BDHASH}-rollback/INDEX-OLD || return 1
|
|
|
|
# Install old files, delete new files, and update linker.hints
|
|
rollback_files ${BDHASH}-rollback || return 1
|
|
|
|
# Remove the rollback directory and the symlink pointing to it; and
|
|
# rearrange bits to allow the previous set of updates to be rolled
|
|
# back next.
|
|
rollback_setup_rollback
|
|
|
|
echo " done."
|
|
}
|
|
|
|
# Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences.
|
|
IDS_compare () {
|
|
# Get all the lines which mismatch in something other than file
|
|
# flags. We ignore file flags because sysinstall doesn't seem to
|
|
# set them when it installs FreeBSD; warning about these adds a
|
|
# very large amount of noise.
|
|
cut -f 1-5,7-8 -d '|' $1 > $1.noflags
|
|
sort -k 1,1 -t '|' $1.noflags > $1.sorted
|
|
cut -f 1-5,7-8 -d '|' $2 |
|
|
comm -13 $1.noflags - |
|
|
fgrep -v '|-|||||' |
|
|
sort -k 1,1 -t '|' |
|
|
join -t '|' $1.sorted - > INDEX-NOTMATCHING
|
|
|
|
# Ignore files which match IDSIGNOREPATHS.
|
|
for X in ${IDSIGNOREPATHS}; do
|
|
grep -E "^${X}" INDEX-NOTMATCHING
|
|
done |
|
|
sort -u |
|
|
comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp
|
|
mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
|
|
|
|
# Go through the lines and print warnings.
|
|
local IFS='|'
|
|
while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do
|
|
# Warn about different object types.
|
|
if ! [ "${TYPE}" = "${P_TYPE}" ]; then
|
|
echo -n "${FPATH} is a "
|
|
case "${P_TYPE}" in
|
|
f) echo -n "regular file, "
|
|
;;
|
|
d) echo -n "directory, "
|
|
;;
|
|
L) echo -n "symlink, "
|
|
;;
|
|
esac
|
|
echo -n "but should be a "
|
|
case "${TYPE}" in
|
|
f) echo -n "regular file."
|
|
;;
|
|
d) echo -n "directory."
|
|
;;
|
|
L) echo -n "symlink."
|
|
;;
|
|
esac
|
|
echo
|
|
|
|
# Skip other tests, since they don't make sense if
|
|
# we're comparing different object types.
|
|
continue
|
|
fi
|
|
|
|
# Warn about different owners.
|
|
if ! [ "${OWNER}" = "${P_OWNER}" ]; then
|
|
echo -n "${FPATH} is owned by user id ${P_OWNER}, "
|
|
echo "but should be owned by user id ${OWNER}."
|
|
fi
|
|
|
|
# Warn about different groups.
|
|
if ! [ "${GROUP}" = "${P_GROUP}" ]; then
|
|
echo -n "${FPATH} is owned by group id ${P_GROUP}, "
|
|
echo "but should be owned by group id ${GROUP}."
|
|
fi
|
|
|
|
# Warn about different permissions. We do not warn about
|
|
# different permissions on symlinks, since some archivers
|
|
# don't extract symlink permissions correctly and they are
|
|
# ignored anyway.
|
|
if ! [ "${PERM}" = "${P_PERM}" ] &&
|
|
! [ "${TYPE}" = "L" ]; then
|
|
echo -n "${FPATH} has ${P_PERM} permissions, "
|
|
echo "but should have ${PERM} permissions."
|
|
fi
|
|
|
|
# Warn about different file hashes / symlink destinations.
|
|
if ! [ "${HASH}" = "${P_HASH}" ]; then
|
|
if [ "${TYPE}" = "L" ]; then
|
|
echo -n "${FPATH} is a symlink to ${P_HASH}, "
|
|
echo "but should be a symlink to ${HASH}."
|
|
fi
|
|
if [ "${TYPE}" = "f" ]; then
|
|
echo -n "${FPATH} has SHA256 hash ${P_HASH}, "
|
|
echo "but should have SHA256 hash ${HASH}."
|
|
fi
|
|
fi
|
|
|
|
# We don't warn about different hard links, since some
|
|
# some archivers break hard links, and as long as the
|
|
# underlying data is correct they really don't matter.
|
|
done < INDEX-NOTMATCHING
|
|
|
|
# Clean up
|
|
rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING
|
|
}
|
|
|
|
# Do the work involved in comparing the system to a "known good" index
|
|
IDS_run () {
|
|
workdir_init || return 1
|
|
|
|
# Prepare the mirror list.
|
|
fetch_pick_server_init && fetch_pick_server
|
|
|
|
# Try to fetch the public key until we run out of servers.
|
|
while ! fetch_key; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
|
|
# Try to fetch the metadata index signature ("tag") until we run
|
|
# out of available servers; and sanity check the downloaded tag.
|
|
while ! fetch_tag; do
|
|
fetch_pick_server || return 1
|
|
done
|
|
fetch_tagsanity || return 1
|
|
|
|
# Fetch INDEX-OLD and INDEX-ALL.
|
|
fetch_metadata INDEX-OLD INDEX-ALL || return 1
|
|
|
|
# Generate filtered INDEX-OLD and INDEX-ALL files containing only
|
|
# the components we want and without anything marked as "Ignore".
|
|
fetch_filter_metadata INDEX-OLD || return 1
|
|
fetch_filter_metadata INDEX-ALL || return 1
|
|
|
|
# Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL.
|
|
sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp
|
|
mv INDEX-ALL.tmp INDEX-ALL
|
|
rm INDEX-OLD
|
|
|
|
# Translate /boot/${KERNCONF} to ${KERNELDIR}
|
|
fetch_filter_kernel_names INDEX-ALL ${KERNCONF}
|
|
|
|
# Inspect the system and generate an INDEX-PRESENT file.
|
|
fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1
|
|
|
|
# Compare INDEX-ALL and INDEX-PRESENT and print warnings about any
|
|
# differences.
|
|
IDS_compare INDEX-ALL INDEX-PRESENT
|
|
}
|
|
|
|
#### Main functions -- call parameter-handling and core functions
|
|
|
|
# Using the command line, configuration file, and defaults,
|
|
# set all the parameters which are needed later.
|
|
get_params () {
|
|
init_params
|
|
parse_cmdline $@
|
|
parse_conffile
|
|
default_params
|
|
}
|
|
|
|
# Fetch command. Make sure that we're being called
|
|
# interactively, then run fetch_check_params and fetch_run
|
|
cmd_fetch () {
|
|
finalize_components_config ${COMPONENTS}
|
|
if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
|
|
echo -n "`basename $0` fetch should not "
|
|
echo "be run non-interactively."
|
|
echo "Run `basename $0` cron instead."
|
|
exit 1
|
|
fi
|
|
fetch_check_params
|
|
fetch_run || exit 1
|
|
ISFETCHED=1
|
|
}
|
|
|
|
# Cron command. Make sure the parameters are sensible; wait
|
|
# rand(3600) seconds; then fetch updates. While fetching updates,
|
|
# send output to a temporary file; only print that file if the
|
|
# fetching failed.
|
|
cmd_cron () {
|
|
fetch_check_params
|
|
sleep `jot -r 1 0 3600`
|
|
|
|
TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
|
|
finalize_components_config ${COMPONENTS} >> ${TMPFILE}
|
|
if ! fetch_run >> ${TMPFILE} ||
|
|
! grep -q "No updates needed" ${TMPFILE} ||
|
|
[ ${VERBOSELEVEL} = "debug" ]; then
|
|
mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
|
|
fi
|
|
|
|
rm ${TMPFILE}
|
|
}
|
|
|
|
# Fetch files for upgrading to a new release.
|
|
cmd_upgrade () {
|
|
finalize_components_config ${COMPONENTS}
|
|
upgrade_check_params
|
|
upgrade_run || exit 1
|
|
}
|
|
|
|
# Check if there are fetched updates ready to install.
|
|
# Chdir into the working directory.
|
|
cmd_updatesready () {
|
|
finalize_components_config ${COMPONENTS}
|
|
# Check if working directory exists (if not, no updates pending)
|
|
if ! [ -e "${WORKDIR}" ]; then
|
|
echo "No updates are available to install."
|
|
exit 2
|
|
fi
|
|
|
|
# Change into working directory (fail if no permission/directory etc.)
|
|
cd ${WORKDIR} || exit 1
|
|
|
|
# Construct a unique name from ${BASEDIR}
|
|
BDHASH=`echo ${BASEDIR} | sha256 -q`
|
|
|
|
# Check that we have updates ready to install
|
|
if ! [ -L ${BDHASH}-install ]; then
|
|
echo "No updates are available to install."
|
|
exit 2
|
|
fi
|
|
|
|
echo "There are updates available to install."
|
|
echo "Run '$0 install' to proceed."
|
|
}
|
|
|
|
# Install downloaded updates.
|
|
cmd_install () {
|
|
finalize_components_config ${COMPONENTS}
|
|
install_check_params
|
|
install_create_be
|
|
install_run || exit 1
|
|
}
|
|
|
|
# Rollback most recently installed updates.
|
|
cmd_rollback () {
|
|
finalize_components_config ${COMPONENTS}
|
|
rollback_check_params
|
|
rollback_run || exit 1
|
|
}
|
|
|
|
# Compare system against a "known good" index.
|
|
cmd_IDS () {
|
|
finalize_components_config ${COMPONENTS}
|
|
IDS_check_params
|
|
IDS_run || exit 1
|
|
}
|
|
|
|
# Output configuration.
|
|
cmd_showconfig () {
|
|
finalize_components_config ${COMPONENTS}
|
|
for X in ${CONFIGOPTIONS}; do
|
|
echo $X=$(eval echo \$${X})
|
|
done
|
|
}
|
|
|
|
#### Entry point
|
|
|
|
# Make sure we find utilities from the base system
|
|
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
|
|
|
|
# Set a pager if the user doesn't
|
|
if [ -z "$PAGER" ]; then
|
|
PAGER=/usr/bin/less
|
|
fi
|
|
|
|
# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
|
|
export LC_ALL=C
|
|
|
|
# Clear environment variables that may affect operation of tools that we use.
|
|
unset GREP_OPTIONS
|
|
|
|
get_params $@
|
|
for COMMAND in ${COMMANDS}; do
|
|
cmd_${COMMAND}
|
|
done
|