diff --git a/CHANGELOG b/CHANGELOG index e60e1e8..47f90cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ ====================== Version Description +1.2.25......Add 'etcupdate' missing command, update bundled files. 1.2.24......Minor code changes/improvements. 1.2.23......Check/update bastille config parameters on runtime. 1.2.22......Make sure minor changes are always applied. diff --git a/bastille-init b/bastille-init index 5290996..847fbeb 100755 --- a/bastille-init +++ b/bastille-init @@ -470,6 +470,7 @@ include_files() # Include missing system files. # Symlink the files in embedded platforms. USRBIN_FILES="ar diff3 makewhatis setfib sum" + USRSBIN_FILES="etcupdate" LOCALBIN_FILES="jib" if [ "${PRDPLATFORM}" = "x64-embedded" ]; then for _usrbin_file in ${USRBIN_FILES}; do @@ -481,6 +482,15 @@ include_files() ln -fhs ${SYSTEM_INCLUDE}/${_usrbin_file} /usr/bin/${_usrbin_file} fi done + for _usrsbin_file in ${USRSBIN_FILES}; do + if [ -f "/usr/sbin/${_usrsbin_file}" ] && [ ! -L "/usr/sbin/${_usrsbin_file}" ]; then + rm -r /usr/sbin/${_usrsbin_file} + fi + if [ ! -f "/usr/sbin/${_usrsbin_file}" ]; then + chmod 0555 "${SYSTEM_INCLUDE}/${_usrsbin_file}" + ln -fhs ${SYSTEM_INCLUDE}/${_usrsbin_file} /usr/sbin/${_usrsbin_file} + fi + done for _localbin_file in ${LOCALBIN_FILES}; do if [ -f "/usr/local/bin/${_localbin_file}" ] && [ ! -L "/usr/local/bin/${_localbin_file}" ]; then rm -r /usr/local/bin/${_localbin_file} @@ -497,6 +507,11 @@ include_files() install -m 0555 "${SYSTEM_INCLUDE}/${_usrbin_file}" /usr/bin/${_usrbin_file} fi done + for _usrsbin_file in ${USRSBIN_FILES}; do + if [ ! -f "/usr/sbin/${_usrsbin_file}" ]; then + install -m 0555 "${SYSTEM_INCLUDE}/${_usrsbin_file}" /usr/sbin/${_usrsbin_file} + fi + done for _localbin_file in ${LOCALBIN_FILES}; do if [ ! -f "/usr/local/bin/${_localbin_file}" ]; then install -m 0544 ${SYSTEM_INCLUDE}/${_localbin_file} /usr/local/bin/${_localbin_file} @@ -993,7 +1008,7 @@ zfs_activate() done echo "Proceeding..." - if [ "${bastille_zfs_enable}" = "YES" ]; then + if [ "${bastille_zfs_enable}" = "YES" ] || [ "${bastille_zfs_enable}" = "yes" ]; then if [ -n "${bastille_zfs_zpool}" ]; then if zfs list "${bastille_zfs_zpool}" > /dev/null 2>&1; then if ! zfs list "${bastille_zfs_zpool}/${bastille_zfs_prefix}" > /dev/null 2>&1; then @@ -1343,7 +1358,7 @@ rc_params() fi # Check for sane ZFS parameters in this setup. - if [ "${bastille_zfs_enable}" = "YES" ]; then + if [ "${bastille_zfs_enable}" = "YES" ] || [ "${bastille_zfs_enable}" = "yes" ]; then if [ -n "${bastille_zfs_zpool}" ]; then if zfs list "${bastille_zfs_zpool}" >/dev/null 2>&1; then @@ -1397,7 +1412,7 @@ rc_params() fi else # Check for orphaned configuration and/or config reset. - if zfs list -H "${CWDIR}" | awk '{print $1}' | grep -qw "${DAFAULT_BASTILLE_PREFIX}"; then + if zfs list -H "${CWDIR}" 2>/dev/null | awk '{print $1}' | grep -qw "${DAFAULT_BASTILLE_PREFIX}"; then zfs_support_error else if [ "${bastille_zfs_enable}" = "NO" ] || [ "${bastille_zfs_enable}" = "no" ]; then diff --git a/conf/system/freebsd-update/14.3/freebsd-update b/conf/system/freebsd-update/14.3/freebsd-update index 77b12a3..cf314a2 100755 --- a/conf/system/freebsd-update/14.3/freebsd-update +++ b/conf/system/freebsd-update/14.3/freebsd-update @@ -1046,6 +1046,26 @@ IDS_check_params () { fetch_setup_verboselevel } +# Packaged base and freebsd-update are incompatible. Exit with an error if +# packaged base is in use. +check_pkgbase() +{ + # Packaged base requires that pkg is bootstrapped. + if ! pkg -c ${BASEDIR} -N >/dev/null 2>/dev/null; then + return + fi + # uname(1) is used by pkg to determine ABI, so it should exist. + # If it comes from a package then this system uses packaged base. + if ! pkg -c ${BASEDIR} which /usr/bin/uname >/dev/null; then + return + fi + cat < INDEX-NEW install_from_index INDEX-NEW || return 1 - # Install new shared libraries next + # Next, in order, libsys, libc, and libthr. grep -vE '^/boot/' $1/INDEX-NEW | grep -vE '^[^|]+\|d\|' | grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | + grep -E '^[^|]*/lib/libsys\.so\.[0-9]+\|' > INDEX-NEW + install_from_index INDEX-NEW || return 1 + grep -vE '^/boot/' $1/INDEX-NEW | + grep -vE '^[^|]+\|d\|' | + grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | + grep -E '^[^|]*/lib/libc\.so\.[0-9]+\|' > INDEX-NEW + install_from_index INDEX-NEW || return 1 + grep -vE '^/boot/' $1/INDEX-NEW | + grep -vE '^[^|]+\|d\|' | + grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | + grep -E '^[^|]*/lib/libthr\.so\.[0-9]+\|' > INDEX-NEW + install_from_index INDEX-NEW || return 1 + + # Install the rest of the shared libraries next + grep -vE '^/boot/' $1/INDEX-NEW | + grep -vE '^[^|]+\|d\|' | + grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' | + grep -vE '^[^|]*/lib/(libsys|libc|libthr)\.so\.[0-9]+\|' | grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW install_from_index INDEX-NEW || return 1 @@ -3541,6 +3579,9 @@ export LC_ALL=C # Clear environment variables that may affect operation of tools that we use. unset GREP_OPTIONS +# Disallow use with packaged base. +check_pkgbase + get_params $@ for COMMAND in ${COMMANDS}; do cmd_${COMMAND} diff --git a/conf/system/include/14.3/ar b/conf/system/include/14.3/ar old mode 100755 new mode 100644 index 5d7523b..36c141a Binary files a/conf/system/include/14.3/ar and b/conf/system/include/14.3/ar differ diff --git a/conf/system/include/14.3/diff3 b/conf/system/include/14.3/diff3 old mode 100755 new mode 100644 index 72f3413..4095513 Binary files a/conf/system/include/14.3/diff3 and b/conf/system/include/14.3/diff3 differ diff --git a/conf/system/include/14.3/etcupdate b/conf/system/include/14.3/etcupdate new file mode 100644 index 0000000..7bddd65 --- /dev/null +++ b/conf/system/include/14.3/etcupdate @@ -0,0 +1,1963 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2010-2013 Hudson River Trading LLC +# Written by: John H. Baldwin +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. +# + +# This is a tool to manage updating files that are not updated as part +# of 'make installworld' such as files in /etc. Unlike other tools, +# this one is specifically tailored to assisting with mass upgrades. +# To that end it does not require user intervention while running. +# +# Theory of operation: +# +# The most reliable way to update changes to files that have local +# modifications is to perform a three-way merge between the original +# unmodified file, the new version of the file, and the modified file. +# This requires having all three versions of the file available when +# performing an update. +# +# To that end, etcupdate uses a strategy where the current unmodified +# tree is kept in WORKDIR/current and the previous unmodified tree is +# kept in WORKDIR/old. When performing a merge, a new tree is built +# if needed and then the changes are merged into DESTDIR. Any files +# with unresolved conflicts after the merge are left in a tree rooted +# at WORKDIR/conflicts. +# +# To provide extra flexibility, etcupdate can also build tarballs of +# root trees that can later be used. It can also use a tarball as the +# source of a new tree instead of building it from /usr/src. + +# Global settings. These can be adjusted by config files and in some +# cases by command line options. + +# TODO: +# - automatable conflict resolution + +usage() +{ + cat < + etcupdate diff [-d workdir] [-D destdir] [-I patterns] [-L logfile] + etcupdate extract [-BN] [-d workdir] [-s source | -t tarball] + [-D destdir] [-L logfile] [-M options] [-m make] + etcupdate resolve [-p] [-d workdir] [-D destdir] [-L logfile] + etcupdate revert [-d workdir] [-D destdir] [-L logfile] file ... + etcupdate status [-d workdir] [-D destdir] +EOF + exit 1 +} + +# Used to write a message prepended with '>>>' to the logfile. +log() +{ + echo ">>>" "$@" >&3 +} + +# Used for assertion conditions that should never happen. +panic() +{ + echo "PANIC:" "$@" + exit 10 +} + +# Used to write a warning message. These are saved to the WARNINGS +# file with " " prepended. +warn() +{ + echo -n " " >> $WARNINGS + echo "$@" >> $WARNINGS +} + +# Output a horizontal rule using the passed-in character. Matches the +# length used for Index lines in CVS and SVN diffs. +# +# $1 - character +rule() +{ + jot -b "$1" -s "" 67 +} + +# Output a text description of a specified file's type. +# +# $1 - file pathname. +file_type() +{ + stat -f "%HT" $1 | tr "[:upper:]" "[:lower:]" +} + +# Returns true (0) if a file exists +# +# $1 - file pathname. +exists() +{ + [ -e $1 -o -L $1 ] +} + +# Returns true (0) if a file should be ignored, false otherwise. +# +# $1 - file pathname +ignore() +{ + local pattern - + + set -o noglob + for pattern in $IGNORE_FILES; do + set +o noglob + case $1 in + $pattern) + return 0 + ;; + esac + set -o noglob + done + + # Ignore /.cshrc and /.profile if they are hardlinked to the + # same file in /root. This ensures we only compare those + # files once in that case. + case $1 in + /.cshrc|/.profile) + if [ ${DESTDIR}$1 -ef ${DESTDIR}/root$1 ]; then + return 0 + fi + ;; + *) + ;; + esac + + return 1 +} + +# Returns true (0) if the new version of a file should always be +# installed rather than attempting to do a merge. +# +# $1 - file pathname +always_install() +{ + local pattern - + + set -o noglob + for pattern in $ALWAYS_INSTALL; do + set +o noglob + case $1 in + $pattern) + return 0 + ;; + esac + set -o noglob + done + + return 1 +} + +# Build a new tree. This runs inside a subshell to trap SIGINT. +# +# $1 - directory to store new tree in +build_tree() +( + local destdir dir file make autogenfiles metatmp + + make="$MAKE_CMD $MAKE_OPTIONS -DNO_FILEMON" + + if [ -n "$noroot" ]; then + make="$make -DNO_ROOT" + metatmp=`mktemp $WORKDIR/etcupdate-XXXXXXX` + : > $metatmp + trap "rm -f $metatmp; trap '' EXIT; return 1" INT + trap "rm -f $metatmp" EXIT + else + metatmp="/dev/null" + trap "return 1" INT + fi + + log "Building tree at $1 with $make" + + exec >&3 2>&1 + + mkdir -p $1/usr/obj + destdir=`realpath $1` + + if [ -n "$preworld" ]; then + # Build a limited tree that only contains files that are + # crucial to installworld. + for file in $PREWORLD_FILES; do + name=$(basename $file) + mkdir -p $1/etc || return 1 + cp -p $SRCDIR/$file $1/etc/$name || return 1 + done + else + ( + cd $SRCDIR || exit 1 + if ! [ -n "$nobuild" ]; then + export MAKEOBJDIRPREFIX=$destdir/usr/obj + if [ -n "$($make -V.ALLTARGETS:Mbuildetc)" ]; then + $make buildetc || exit 1 + else + $make _obj SUBDIR_OVERRIDE=etc || exit 1 + $make everything SUBDIR_OVERRIDE=etc || exit 1 + fi + fi + if [ -n "$($make -V.ALLTARGETS:Minstalletc)" ]; then + $make DESTDIR=$destdir installetc || exit 1 + else + $make DESTDIR=$destdir distrib-dirs || exit 1 + $make DESTDIR=$destdir distribution || exit 1 + fi + ) || return 1 + fi + chflags -R noschg $1 || return 1 + rm -rf $1/usr/obj || return 1 + + # Purge auto-generated files. Only the source files need to + # be updated after which these files are regenerated. + autogenfiles="./etc/*.db ./etc/passwd ./var/db/services.db" + (cd $1 && printf '%s\n' $autogenfiles >> $metatmp && \ + rm -f $autogenfiles) || return 1 + + # Remove empty files. These just clutter the output of 'diff'. + (cd $1 && find . -type f -size 0 -delete -print >> $metatmp) || \ + return 1 + + # Trim empty directories. + (cd $1 && find . -depth -type d -empty -delete -print >> $metatmp) || \ + return 1 + + if [ -n "$noroot" ]; then + # Rewrite the METALOG to exclude the files (and directories) + # removed above. $metatmp contains the list of files to delete, + # and we append #METALOG# as a delimiter followed by the + # original METALOG. This lets us scan through $metatmp in awk + # building up a table of names to delete until we reach the + # delimiter, then emit all the entries of the original METALOG + # after it that aren't in that table. We also exclude ./usr/obj + # and its children explicitly for simplicity rather than + # building up that list (and in practice only ./usr/obj itself + # will be in the METALOG since nothing is installed there). + echo '#METALOG#' >> $metatmp || return 1 + cat $1/METALOG >> $metatmp || return 1 + awk '/^#METALOG#$/ { metalog = 1; next } + { f=$1; gsub(/\/\/+/, "/", f) } + !metalog { rm[f] = 1; next } + !rm[f] && f !~ /^\.\/usr\/obj(\/|$)/ { print }' \ + $metatmp > $1/METALOG || return 1 + fi + + return 0 +) + +# Generate a new tree. If tarball is set, then the tree is +# extracted from the tarball. Otherwise the tree is built from a +# source tree. +# +# $1 - directory to store new tree in +extract_tree() +{ + local files + + # If we have a tarball, extract that into the new directory. + if [ -n "$tarball" ]; then + files= + if [ -n "$preworld" ]; then + files="$PREWORLD_FILES" + fi + if ! (mkdir -p $1 && tar xf $tarball -C $1 $files) \ + >&3 2>&1; then + echo "Failed to extract new tree." + remove_tree $1 + exit 1 + fi + else + if ! build_tree $1; then + echo "Failed to build new tree." + remove_tree $1 + exit 1 + fi + fi +} + +# Forcefully remove a tree. Returns true (0) if the operation succeeds. +# +# $1 - path to tree +remove_tree() +{ + + rm -rf $1 >&3 2>&1 + if [ -e $1 ]; then + chflags -R noschg $1 >&3 2>&1 + rm -rf $1 >&3 2>&1 + fi + [ ! -e $1 ] +} + +# Return values for compare() +COMPARE_EQUAL=0 +COMPARE_ONLYFIRST=1 +COMPARE_ONLYSECOND=2 +COMPARE_DIFFTYPE=3 +COMPARE_DIFFLINKS=4 +COMPARE_DIFFFILES=5 + +# Compare two files/directories/symlinks. Note that this does not +# recurse into subdirectories. Instead, if two nodes are both +# directories, they are assumed to be equivalent. +# +# Returns true (0) if the nodes are identical. If only one of the two +# nodes are present, return one of the COMPARE_ONLY* constants. If +# the nodes are different, return one of the COMPARE_DIFF* constants +# to indicate the type of difference. +# +# $1 - first node +# $2 - second node +compare() +{ + local first second + + # If the first node doesn't exist, then check for the second + # node. Note that -e will fail for a symbolic link that + # points to a missing target. + if ! exists $1; then + if exists $2; then + return $COMPARE_ONLYSECOND + else + return $COMPARE_EQUAL + fi + elif ! exists $2; then + return $COMPARE_ONLYFIRST + fi + + # If the two nodes are different file types fail. + first=`stat -f "%Hp" $1` + second=`stat -f "%Hp" $2` + if [ "$first" != "$second" ]; then + return $COMPARE_DIFFTYPE + fi + + # If both are symlinks, compare the link values. + if [ -L $1 ]; then + first=`readlink $1` + second=`readlink $2` + if [ "$first" = "$second" ]; then + return $COMPARE_EQUAL + else + return $COMPARE_DIFFLINKS + fi + fi + + # If both are files, compare the file contents. + if [ -f $1 ]; then + if cmp -s $1 $2; then + return $COMPARE_EQUAL + else + return $COMPARE_DIFFFILES + fi + fi + + # As long as the two nodes are the same type of file, consider + # them equivalent. + return $COMPARE_EQUAL +} + +# Returns true (0) if the only difference between two regular files is a +# change in the FreeBSD ID string. +# +# $1 - path of first file +# $2 - path of second file +fbsdid_only() +{ + + diff -qI '\$FreeBSD.*\$' $1 $2 >/dev/null 2>&1 +} + +# This is a wrapper around compare that will return COMPARE_EQUAL if +# the only difference between two regular files is a change in the +# FreeBSD ID string. It only makes this adjustment if the -F flag has +# been specified. +# +# $1 - first node +# $2 - second node +compare_fbsdid() +{ + local cmp + + compare $1 $2 + cmp=$? + + if [ -n "$FREEBSD_ID" -a "$cmp" -eq $COMPARE_DIFFFILES ] && \ + fbsdid_only $1 $2; then + return $COMPARE_EQUAL + fi + + return $cmp +} + +# Returns true (0) if a directory is empty. +# +# $1 - pathname of the directory to check +empty_dir() +{ + local contents + + contents=`ls -A $1` + [ -z "$contents" ] +} + +# Returns true (0) if one directories contents are a subset of the +# other. This will recurse to handle subdirectories and compares +# individual files in the trees. Its purpose is to quiet spurious +# directory warnings for dryrun invocations. +# +# $1 - first directory (sub) +# $2 - second directory (super) +dir_subset() +{ + local contents file + + if ! [ -d $1 -a -d $2 ]; then + return 1 + fi + + # Ignore files that are present in the second directory but not + # in the first. + contents=`ls -A $1` + for file in $contents; do + if ! compare $1/$file $2/$file; then + return 1 + fi + + if [ -d $1/$file ]; then + if ! dir_subset $1/$file $2/$file; then + return 1 + fi + fi + done + return 0 +} + +# Returns true (0) if a directory in the destination tree is empty. +# If this is a dryrun, then this returns true as long as the contents +# of the directory are a subset of the contents in the old tree +# (meaning that the directory would be empty in a non-dryrun when this +# was invoked) to quiet spurious warnings. +# +# $1 - pathname of the directory to check relative to DESTDIR. +empty_destdir() +{ + + if [ -n "$dryrun" ]; then + dir_subset $DESTDIR/$1 $OLDTREE/$1 + return + fi + + empty_dir $DESTDIR/$1 +} + +# Output a diff of two directory entries with the same relative name +# in different trees. Note that as with compare(), this does not +# recurse into subdirectories. If the nodes are identical, nothing is +# output. +# +# $1 - first tree +# $2 - second tree +# $3 - node name +# $4 - label for first tree +# $5 - label for second tree +diffnode() +{ + local first second file old new diffargs + + if [ -n "$FREEBSD_ID" ]; then + diffargs="-I \\\$FreeBSD.*\\\$" + else + diffargs="" + fi + + compare_fbsdid $1/$3 $2/$3 + case $? in + $COMPARE_EQUAL) + ;; + $COMPARE_ONLYFIRST) + echo + echo "Removed: $3" + echo + ;; + $COMPARE_ONLYSECOND) + echo + echo "Added: $3" + echo + ;; + $COMPARE_DIFFTYPE) + first=`file_type $1/$3` + second=`file_type $2/$3` + echo + echo "Node changed from a $first to a $second: $3" + echo + ;; + $COMPARE_DIFFLINKS) + first=`readlink $1/$file` + second=`readlink $2/$file` + echo + echo "Link changed: $file" + rule "=" + echo "-$first" + echo "+$second" + echo + ;; + $COMPARE_DIFFFILES) + echo "Index: $3" + rule "=" + diff -u $diffargs -L "$3 ($4)" $1/$3 -L "$3 ($5)" $2/$3 + ;; + esac +} + +# Run one-off commands after an update has completed. These commands +# are not tied to a specific file, so they cannot be handled by +# post_install_file(). +post_update() +{ + local args + + # None of these commands should be run for a pre-world update. + if [ -n "$preworld" ]; then + return + fi + + # If /etc/localtime exists and is not a symlink and /var/db/zoneinfo + # exists, run tzsetup -r to refresh /etc/localtime. + if [ -f ${DESTDIR}/etc/localtime -a \ + ! -L ${DESTDIR}/etc/localtime ]; then + if [ -f ${DESTDIR}/var/db/zoneinfo ]; then + if [ -n "${DESTDIR}" ]; then + args="-C ${DESTDIR}" + else + args="" + fi + log "tzsetup -r ${args}" + if [ -z "$dryrun" ]; then + tzsetup -r ${args} >&3 2>&1 + fi + else + warn "Needs update: /etc/localtime (required" \ + "manual update via tzsetup(8))" + fi + fi +} + +# Create missing parent directories of a node in a target tree +# preserving the owner, group, and permissions from a specified +# template tree. +# +# $1 - template tree +# $2 - target tree +# $3 - pathname of the node (relative to both trees) +install_dirs() +{ + local args dir + + dir=`dirname $3` + + # Nothing to do if the parent directory exists. This also + # catches the degenerate cases when the path is just a simple + # filename. + if [ -d ${2}$dir ]; then + return 0 + fi + + # If non-directory file exists with the desired directory + # name, then fail. + if exists ${2}$dir; then + # If this is a dryrun and we are installing the + # directory in the DESTDIR and the file in the DESTDIR + # matches the file in the old tree, then fake success + # to quiet spurious warnings. + if [ -n "$dryrun" -a "$2" = "$DESTDIR" ]; then + if compare $OLDTREE/$dir $DESTDIR/$dir; then + return 0 + fi + fi + + args=`file_type ${2}$dir` + warn "Directory mismatch: ${2}$dir ($args)" + return 1 + fi + + # Ensure the parent directory of the directory is present + # first. + if ! install_dirs $1 "$2" $dir; then + return 1 + fi + + # Format attributes from template directory as install(1) + # arguments. + args=`stat -f "-o %Su -g %Sg -m %0Mp%0Lp" $1/$dir` + + log "install -d $args ${2}$dir" + if [ -z "$dryrun" ]; then + install -d $args ${2}$dir >&3 2>&1 + fi + return 0 +} + +# Perform post-install fixups for a file. This largely consists of +# regenerating any files that depend on the newly installed file. +# +# $1 - pathname of the updated file (relative to DESTDIR) +post_install_file() +{ + case $1 in + /etc/mail/aliases) + # Grr, newaliases only works for an empty DESTDIR. + if [ -z "$DESTDIR" ]; then + log "newaliases" + if [ -z "$dryrun" ]; then + newaliases >&3 2>&1 + fi + else + NEWALIAS_WARN=yes + fi + ;; + /usr/share/certs/trusted/* | /usr/share/certs/untrusted/*) + log "certctl rehash" + if [ -z "$dryrun" ]; then + env DESTDIR=${DESTDIR} certctl rehash >&3 2>&1 + fi + ;; + /etc/login.conf) + log "cap_mkdb ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + cap_mkdb ${DESTDIR}$1 >&3 2>&1 + fi + ;; + /etc/master.passwd) + log "pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1 \ + >&3 2>&1 + fi + ;; + /etc/motd) + # /etc/rc.d/motd hardcodes the /etc/motd path. + # Don't warn about non-empty DESTDIR's since this + # change is only cosmetic anyway. + if [ -z "$DESTDIR" ]; then + log "sh /etc/rc.d/motd start" + if [ -z "$dryrun" ]; then + sh /etc/rc.d/motd start >&3 2>&1 + fi + fi + ;; + /etc/services) + log "services_mkdb -q -o $DESTDIR/var/db/services.db" \ + "${DESTDIR}$1" + if [ -z "$dryrun" ]; then + services_mkdb -q -o $DESTDIR/var/db/services.db \ + ${DESTDIR}$1 >&3 2>&1 + fi + ;; + esac +} + +# Install the "new" version of a file. Returns true if it succeeds +# and false otherwise. +# +# $1 - pathname of the file to install (relative to DESTDIR) +install_new() +{ + + if ! install_dirs $NEWTREE "$DESTDIR" $1; then + return 1 + fi + log "cp -Rp ${NEWTREE}$1 ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + cp -Rp ${NEWTREE}$1 ${DESTDIR}$1 >&3 2>&1 + fi + post_install_file $1 + return 0 +} + +# Install the "resolved" version of a file. Returns true if it succeeds +# and false otherwise. +# +# $1 - pathname of the file to install (relative to DESTDIR) +install_resolved() +{ + + # This should always be present since the file is already + # there (it caused a conflict). However, it doesn't hurt to + # just be safe. + if ! install_dirs $NEWTREE "$DESTDIR" $1; then + return 1 + fi + + # Use cat rather than cp to preserve metadata + log "cat ${CONFLICTS}$1 > ${DESTDIR}$1" + cat ${CONFLICTS}$1 > ${DESTDIR}$1 2>&3 + post_install_file $1 + return 0 +} + +# Generate a conflict file when a "new" file conflicts with an +# existing file in DESTDIR. +# +# $1 - pathname of the file that conflicts (relative to DESTDIR) +new_conflict() +{ + + if [ -n "$dryrun" ]; then + return + fi + + install_dirs $NEWTREE $CONFLICTS $1 + diff --changed-group-format='<<<<<<< (local) +%<======= +%>>>>>>>> (stock) +' $DESTDIR/$1 $NEWTREE/$1 > $CONFLICTS/$1 +} + +# Remove the "old" version of a file. +# +# $1 - pathname of the old file to remove (relative to DESTDIR) +remove_old() +{ + log "rm -f ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + rm -f ${DESTDIR}$1 >&3 2>&1 + fi + echo " D $1" +} + +# Update a file that has no local modifications. +# +# $1 - pathname of the file to update (relative to DESTDIR) +update_unmodified() +{ + local new old + + # If the old file is a directory, then remove it with rmdir + # (this should only happen if the file has changed its type + # from a directory to a non-directory). If the directory + # isn't empty, then fail. This will be reported as a warning + # later. + if [ -d $DESTDIR/$1 ]; then + if empty_destdir $1; then + log "rmdir ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + rmdir ${DESTDIR}$1 >&3 2>&1 + fi + else + return 1 + fi + + # If both the old and new files are regular files, leave the + # existing file. This avoids breaking hard links for /.cshrc + # and /.profile. Otherwise, explicitly remove the old file. + elif ! [ -f ${DESTDIR}$1 -a -f ${NEWTREE}$1 ]; then + log "rm -f ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + rm -f ${DESTDIR}$1 >&3 2>&1 + fi + fi + + # If the new file is a directory, note that the old file has + # been removed, but don't do anything else for now. The + # directory will be installed if needed when new files within + # that directory are installed. + if [ -d $NEWTREE/$1 ]; then + if empty_dir $NEWTREE/$1; then + echo " D $file" + else + echo " U $file" + fi + elif install_new $1; then + echo " U $file" + fi + return 0 +} + +# Update the FreeBSD ID string in a locally modified file to match the +# FreeBSD ID string from the "new" version of the file. +# +# $1 - pathname of the file to update (relative to DESTDIR) +update_freebsdid() +{ + local new dest file + + # If the FreeBSD ID string is removed from the local file, + # there is nothing to do. In this case, treat the file as + # updated. Otherwise, if either file has more than one + # FreeBSD ID string, just punt and let the user handle the + # conflict manually. + new=`grep -c '\$FreeBSD.*\$' ${NEWTREE}$1` + dest=`grep -c '\$FreeBSD.*\$' ${DESTDIR}$1` + if [ "$dest" -eq 0 ]; then + return 0 + fi + if [ "$dest" -ne 1 -o "$dest" -ne 1 ]; then + return 1 + fi + + # If the FreeBSD ID string in the new file matches the FreeBSD ID + # string in the local file, there is nothing to do. + new=`grep '\$FreeBSD.*\$' ${NEWTREE}$1` + dest=`grep '\$FreeBSD.*\$' ${DESTDIR}$1` + if [ "$new" = "$dest" ]; then + return 0 + fi + + # Build the new file in three passes. First, copy all the + # lines preceding the FreeBSD ID string from the local version + # of the file. Second, append the FreeBSD ID string line from + # the new version. Finally, append all the lines after the + # FreeBSD ID string from the local version of the file. + file=`mktemp $WORKDIR/etcupdate-XXXXXXX` + awk '/\$FreeBSD.*\$/ { exit } { print }' ${DESTDIR}$1 >> $file + awk '/\$FreeBSD.*\$/ { print }' ${NEWTREE}$1 >> $file + awk '/\$FreeBSD.*\$/ { ok = 1; next } { if (ok) print }' \ + ${DESTDIR}$1 >> $file + + # As an extra sanity check, fail the attempt if the updated + # version of the file has any differences aside from the + # FreeBSD ID string. + if ! fbsdid_only ${DESTDIR}$1 $file; then + rm -f $file + return 1 + fi + + log "cp $file ${DESTDIR}$1" + if [ -z "$dryrun" ]; then + cp $file ${DESTDIR}$1 >&3 2>&1 + fi + rm -f $file + post_install_file $1 + echo " M $1" + return 0 +} + +# Attempt to update a file that has local modifications. This routine +# only handles regular files. If the 3-way merge succeeds without +# conflicts, the updated file is installed. If the merge fails, the +# merged version with conflict markers is left in the CONFLICTS tree. +# +# $1 - pathname of the file to merge (relative to DESTDIR) +merge_file() +{ + local res + + # Try the merge to see if there is a conflict. + diff3 -E -m ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 > /dev/null 2>&3 + res=$? + case $res in + 0) + # No conflicts, so just redo the merge to the + # real file. + log "diff3 -E -m ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1" + if [ -z "$dryrun" ]; then + temp=$(mktemp -t etcupdate) + diff3 -E -m ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 > ${temp} + # Use "cat >" to preserve metadata. + cat ${temp} > ${DESTDIR}$1 + rm -f ${temp} + fi + post_install_file $1 + echo " M $1" + ;; + 1) + # Conflicts, save a version with conflict markers in + # the conflicts directory. + if [ -z "$dryrun" ]; then + install_dirs $NEWTREE $CONFLICTS $1 + log "diff3 -m ${DESTDIR}$1 ${CONFLICTS}$1" + diff3 -m -L "yours" -L "original" -L "new" \ + ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 > \ + ${CONFLICTS}$1 + fi + echo " C $1" + ;; + *) + panic "merge failed with status $res" + ;; + esac +} + +# Returns true if a file contains conflict markers from a merge conflict. +# +# $1 - pathname of the file to resolve (relative to DESTDIR) +has_conflicts() +{ + + egrep -q '^(<{7}|\|{7}|={7}|>{7}) ' $CONFLICTS/$1 +} + +# Attempt to resolve a conflict. The user is prompted to choose an +# action for each conflict. If the user edits the file, they are +# prompted again for an action. The process is very similar to +# resolving conflicts after an update or merge with Perforce or +# Subversion. The prompts are modelled on a subset of the available +# commands for resolving conflicts with Subversion. +# +# $1 - pathname of the file to resolve (relative to DESTDIR) +resolve_conflict() +{ + local command junk + + echo "Resolving conflict in '$1':" + edit= + while true; do + # Only display the resolved command if the file + # doesn't contain any conflicts. + echo -n "Select: (p) postpone, (df) diff-full, (e) edit," + if ! has_conflicts $1; then + echo -n " (r) resolved," + fi + echo + echo -n " (h) help for more options: " + read command + case $command in + df) + diff -u ${DESTDIR}$1 ${CONFLICTS}$1 + ;; + e) + $EDITOR ${CONFLICTS}$1 + ;; + h) + cat </dev/null 2>&1 + fi + echo " D $dir" + else + warn "Non-empty directory remains: $dir" + fi + fi +} + +# Handle a file that exists in both the old and new trees. If the +# file has not changed in the old and new trees, there is nothing to +# do. If the file in the destination directory matches the new file, +# there is nothing to do. If the file in the destination directory +# matches the old file, then the new file should be installed. +# Everything else becomes some sort of conflict with more detailed +# handling. +# +# $1 - pathname of the file (relative to DESTDIR) +handle_modified_file() +{ + local cmp dest file new newdestcmp old + + file=$1 + if ignore $file; then + log "IGNORE: modified file $file" + return + fi + + compare $OLDTREE/$file $NEWTREE/$file + cmp=$? + if [ $cmp -eq $COMPARE_EQUAL ]; then + return + fi + + if [ $cmp -eq $COMPARE_ONLYFIRST -o $cmp -eq $COMPARE_ONLYSECOND ]; then + panic "Changed file now missing" + fi + + compare $NEWTREE/$file $DESTDIR/$file + newdestcmp=$? + if [ $newdestcmp -eq $COMPARE_EQUAL ]; then + return + fi + + # If the only change in the new file versus the destination + # file is a change in the FreeBSD ID string and -F is + # specified, just install the new file. + if [ -n "$FREEBSD_ID" -a $newdestcmp -eq $COMPARE_DIFFFILES ] && \ + fbsdid_only $NEWTREE/$file $DESTDIR/$file; then + if update_unmodified $file; then + return + else + panic "Updating FreeBSD ID string failed" + fi + fi + + # If the local file is the same as the old file, install the + # new file. If -F is specified and the only local change is + # in the FreeBSD ID string, then install the new file as well. + if compare_fbsdid $OLDTREE/$file $DESTDIR/$file; then + if update_unmodified $file; then + return + fi + fi + + # If the file was removed from the dest tree, just whine. + if [ $newdestcmp -eq $COMPARE_ONLYFIRST ]; then + # If the removed file matches an ALWAYS_INSTALL glob, + # then just install the new version of the file. + if always_install $file; then + log "ALWAYS: adding $file" + if ! [ -d $NEWTREE/$file ]; then + if install_new $file; then + echo " A $file" + fi + fi + return + fi + + # If the only change in the new file versus the old + # file is a change in the FreeBSD ID string and -F is + # specified, don't warn. + if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ + fbsdid_only $OLDTREE/$file $NEWTREE/$file; then + return + fi + + case $cmp in + $COMPARE_DIFFTYPE) + old=`file_type $OLDTREE/$file` + new=`file_type $NEWTREE/$file` + warn "Remove mismatch: $file ($old became $new)" + ;; + $COMPARE_DIFFLINKS) + old=`readlink $OLDTREE/$file` + new=`readlink $NEWTREE/$file` + warn \ + "Removed link changed: $file (\"$old\" became \"$new\")" + ;; + $COMPARE_DIFFFILES) + warn "Removed file changed: $file" + ;; + esac + return + fi + + # Treat the file as unmodified and force install of the new + # file if it matches an ALWAYS_INSTALL glob. If the update + # attempt fails, then fall through to the normal case so a + # warning is generated. + if always_install $file; then + log "ALWAYS: updating $file" + if update_unmodified $file; then + return + fi + fi + + # If the only change in the new file versus the old file is a + # change in the FreeBSD ID string and -F is specified, just + # update the FreeBSD ID string in the local file. + if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ + fbsdid_only $OLDTREE/$file $NEWTREE/$file; then + if update_freebsdid $file; then + continue + fi + fi + + # If the file changed types between the old and new trees but + # the files in the new and dest tree are both of the same + # type, treat it like an added file just comparing the new and + # dest files. + if [ $cmp -eq $COMPARE_DIFFTYPE ]; then + case $newdestcmp in + $COMPARE_DIFFLINKS) + new=`readlink $NEWTREE/$file` + dest=`readlink $DESTDIR/$file` + warn \ + "New link conflict: $file (\"$new\" vs \"$dest\")" + return + ;; + $COMPARE_DIFFFILES) + new_conflict $file + echo " C $file" + return + ;; + esac + else + # If the file has not changed types between the old + # and new trees, but it is a different type in + # DESTDIR, then just warn. + if [ $newdestcmp -eq $COMPARE_DIFFTYPE ]; then + new=`file_type $NEWTREE/$file` + dest=`file_type $DESTDIR/$file` + warn "Modified mismatch: $file ($new vs $dest)" + return + fi + fi + + case $cmp in + $COMPARE_DIFFTYPE) + old=`file_type $OLDTREE/$file` + new=`file_type $NEWTREE/$file` + dest=`file_type $DESTDIR/$file` + warn "Modified $dest changed: $file ($old became $new)" + ;; + $COMPARE_DIFFLINKS) + old=`readlink $OLDTREE/$file` + new=`readlink $NEWTREE/$file` + warn \ + "Modified link changed: $file (\"$old\" became \"$new\")" + ;; + $COMPARE_DIFFFILES) + merge_file $file + ;; + esac +} + +# Handle a file that has been added in the new tree. If the file does +# not exist in DESTDIR, simply copy the file into DESTDIR. If the +# file exists in the DESTDIR and is identical to the new version, do +# nothing. Otherwise, generate a diff of the two versions of the file +# and mark it as a conflict. +# +# $1 - pathname of the file (relative to DESTDIR) +handle_added_file() +{ + local cmp dest file new + + file=$1 + if ignore $file; then + log "IGNORE: added file $file" + return + fi + + compare $DESTDIR/$file $NEWTREE/$file + cmp=$? + case $cmp in + $COMPARE_EQUAL) + return + ;; + $COMPARE_ONLYFIRST) + panic "Added file now missing" + ;; + $COMPARE_ONLYSECOND) + # Ignore new directories. They will be + # created as needed when non-directory nodes + # are installed. + if ! [ -d $NEWTREE/$file ]; then + if install_new $file; then + echo " A $file" + fi + fi + return + ;; + esac + + + # Treat the file as unmodified and force install of the new + # file if it matches an ALWAYS_INSTALL glob. If the update + # attempt fails, then fall through to the normal case so a + # warning is generated. + if always_install $file; then + log "ALWAYS: updating $file" + if update_unmodified $file; then + return + fi + fi + + case $cmp in + $COMPARE_DIFFTYPE) + new=`file_type $NEWTREE/$file` + dest=`file_type $DESTDIR/$file` + warn "New file mismatch: $file ($new vs $dest)" + ;; + $COMPARE_DIFFLINKS) + new=`readlink $NEWTREE/$file` + dest=`readlink $DESTDIR/$file` + warn "New link conflict: $file (\"$new\" vs \"$dest\")" + ;; + $COMPARE_DIFFFILES) + # If the only change in the new file versus + # the destination file is a change in the + # FreeBSD ID string and -F is specified, just + # install the new file. + if [ -n "$FREEBSD_ID" ] && \ + fbsdid_only $NEWTREE/$file $DESTDIR/$file; then + if update_unmodified $file; then + return + else + panic \ + "Updating FreeBSD ID string failed" + fi + fi + + new_conflict $file + echo " C $file" + ;; + esac +} + +# Main routines for each command + +# Build a new tree and save it in a tarball. +build_cmd() +{ + local dir tartree + + if [ $# -ne 1 ]; then + echo "Missing required tarball." + echo + usage + fi + + log "build command: $1" + + # Create a temporary directory to hold the tree + dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` + if [ $? -ne 0 ]; then + echo "Unable to create temporary directory." + exit 1 + fi + if ! build_tree $dir; then + echo "Failed to build tree." + remove_tree $dir + exit 1 + fi + if [ -n "$noroot" ]; then + tartree=@METALOG + else + tartree=. + fi + if ! tar cfj $1 -C $dir $tartree >&3 2>&1; then + echo "Failed to create tarball." + remove_tree $dir + exit 1 + fi + remove_tree $dir +} + +# Output a diff comparing the tree at DESTDIR to the current +# unmodified tree. Note that this diff does not include files that +# are present in DESTDIR but not in the unmodified tree. +diff_cmd() +{ + local file + + if [ $# -ne 0 ]; then + usage + fi + + # Requires an unmodified tree to diff against. + if ! [ -d $NEWTREE ]; then + echo "Reference tree to diff against unavailable." + exit 1 + fi + + # Unfortunately, diff alone does not quite provide the right + # level of options that we want, so improvise. + for file in `(cd $NEWTREE; find .) | sed -e 's/^\.//'`; do + if ignore $file; then + continue + fi + + diffnode $NEWTREE "$DESTDIR" $file "stock" "local" + done +} + +# Just extract a new tree into NEWTREE either by building a tree or +# extracting a tarball. This can be used to bootstrap updates by +# initializing the current "stock" tree to match the currently +# installed system. +# +# Unlike 'update', this command does not rotate or preserve an +# existing NEWTREE, it just replaces any existing tree. +extract_cmd() +{ + + if [ $# -ne 0 ]; then + usage + fi + + log "extract command: tarball=$tarball" + + # Create a temporary directory to hold the tree + dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` + if [ $? -ne 0 ]; then + echo "Unable to create temporary directory." + exit 1 + fi + + extract_tree $dir + + if [ -d $NEWTREE ]; then + if ! remove_tree $NEWTREE; then + echo "Unable to remove current tree." + remove_tree $dir + exit 1 + fi + fi + + if ! mv $dir $NEWTREE >&3 2>&1; then + echo "Unable to rename temp tree to current tree." + remove_tree $dir + exit 1 + fi +} + +# Resolve conflicts left from an earlier merge. +resolve_cmd() +{ + local conflicts + + if [ $# -ne 0 ]; then + usage + fi + + if ! [ -d $CONFLICTS ]; then + return + fi + + if ! [ -d $NEWTREE ]; then + echo "The current tree is not present to resolve conflicts." + exit 1 + fi + + conflicts=`(cd $CONFLICTS; find . ! -type d) | sed -e 's/^\.//'` + for file in $conflicts; do + resolve_conflict $file + done + + if [ -n "$NEWALIAS_WARN" ]; then + warn "Needs update: /etc/mail/aliases.db" \ + "(requires manual update via newaliases(1))" + echo + echo "Warnings:" + echo " Needs update: /etc/mail/aliases.db" \ + "(requires manual update via newaliases(1))" + fi +} + +# Restore files to the stock version. Only files with a local change +# are restored from the stock version. +revert_cmd() +{ + local cmp file + + if [ $# -eq 0 ]; then + usage + fi + + for file; do + log "revert $file" + + if ! [ -e $NEWTREE/$file ]; then + echo "File $file does not exist in the current tree." + exit 1 + fi + if [ -d $NEWTREE/$file ]; then + echo "File $file is a directory." + exit 1 + fi + + compare $DESTDIR/$file $NEWTREE/$file + cmp=$? + if [ $cmp -eq $COMPARE_EQUAL ]; then + continue + fi + + if update_unmodified $file; then + # If this file had a conflict, clean up the + # conflict. + if [ -e $CONFLICTS/$file ]; then + if ! rm $CONFLICTS/$file >&3 2>&1; then + echo "Failed to remove conflict " \ + "for $file". + fi + fi + fi + done +} + +# Report a summary of the previous merge. Specifically, list any +# remaining conflicts followed by any warnings from the previous +# update. +status_cmd() +{ + + if [ $# -ne 0 ]; then + usage + fi + + if [ -d $CONFLICTS ]; then + (cd $CONFLICTS; find . ! -type d) | sed -e 's/^\./ C /' + fi + if [ -s $WARNINGS ]; then + echo "Warnings:" + cat $WARNINGS + fi +} + +# Perform an actual merge. The new tree can either already exist (if +# rerunning a merge), be extracted from a tarball, or generated from a +# source tree. +update_cmd() +{ + local dir new old + + if [ $# -ne 0 ]; then + usage + fi + + log "update command: rerun=$rerun tarball=$tarball preworld=$preworld" + + if [ `id -u` -ne 0 ]; then + echo "Must be root to update a tree." + exit 1 + fi + + # Enforce a sane umask + umask 022 + + # XXX: Should existing conflicts be ignored and removed during + # a rerun? + + # Trim the conflicts tree. Whine if there is anything left. + if [ -e $CONFLICTS ]; then + find -d $CONFLICTS -type d -empty -delete >&3 2>&1 + rmdir $CONFLICTS >&3 2>&1 + fi + if [ -d $CONFLICTS ]; then + echo "Conflicts remain from previous update, aborting." + exit 1 + fi + + # Save tree names to use for rotation later. + old=$OLDTREE + new=$NEWTREE + if [ -z "$rerun" ]; then + # Extract the new tree to a temporary directory. The + # trees are only rotated after a successful update to + # avoid races if an update command is interrupted + # before it completes. + dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` + if [ $? -ne 0 ]; then + echo "Unable to create temporary directory." + exit 1 + fi + + # Populate the new tree. + extract_tree $dir + + # Compare the new tree against the previous tree. For + # the preworld case OLDTREE already points to the + # current stock tree. + if [ -z "$preworld" ]; then + OLDTREE=$NEWTREE + fi + NEWTREE=$dir + fi + + if ! [ -d $OLDTREE ]; then + cat < $WORKDIR/old.files + (cd $NEWTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/new.files + + # Split the files up into three groups using comm. + comm -23 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/removed.files + comm -13 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/added.files + comm -12 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/both.files + + # Initialize conflicts and warnings handling. + rm -f $WARNINGS + mkdir -p $CONFLICTS + if ! chmod 0700 ${CONFLICTS}; then + panic "Unable to set permissions on conflicts directory" + fi + + # Ignore removed files for the pre-world case. A pre-world + # update uses a stripped-down tree. + if [ -n "$preworld" ]; then + > $WORKDIR/removed.files + fi + + # The order for the following sections is important. In the + # odd case that a directory is converted into a file, the + # existing subfiles need to be removed if possible before the + # file is converted. Similarly, in the case that a file is + # converted into a directory, the file needs to be converted + # into a directory if possible before the new files are added. + + # First, handle removed files. + for file in `cat $WORKDIR/removed.files`; do + handle_removed_file $file + done + + # For the directory pass, reverse sort the list to effect a + # depth-first traversal. This is needed to ensure that if a + # directory with subdirectories is removed, the entire + # directory is removed if there are no local modifications. + for file in `sort -r $WORKDIR/removed.files`; do + handle_removed_directory $file + done + + # Second, handle files that exist in both the old and new + # trees. + for file in `cat $WORKDIR/both.files`; do + handle_modified_file $file + done + + # Finally, handle newly added files. + for file in `cat $WORKDIR/added.files`; do + handle_added_file $file + done + + if [ -n "$NEWALIAS_WARN" ]; then + warn "Needs update: /etc/mail/aliases.db" \ + "(requires manual update via newaliases(1))" + fi + + # Run any special one-off commands after an update has completed. + post_update + + if [ -s $WARNINGS ]; then + echo "Warnings:" + cat $WARNINGS + fi + + # If this was a dryrun, remove the temporary tree if we built + # a new one. + if [ -n "$dryrun" ]; then + if [ -n "$dir" ]; then + if [ -n "$rerun" ]; then + panic "Should not have a temporary directory" + fi + remove_tree $dir + fi + return + fi + + # Finally, rotate any needed trees. + if [ "$new" != "$NEWTREE" ]; then + if [ -n "$rerun" ]; then + panic "Should not have a temporary directory" + fi + if [ -z "$dir" ]; then + panic "Should have a temporary directory" + fi + + # Rotate the old tree if needed + if [ "$old" != "$OLDTREE" ]; then + if [ -n "$preworld" ]; then + panic "Old tree should be unchanged" + fi + + if ! remove_tree $old; then + echo "Unable to remove previous old tree." + exit 1 + fi + + if ! mv $OLDTREE $old >&3 2>&1; then + echo "Unable to rename old tree." + exit 1 + fi + fi + + # Rotate the new tree. Remove a previous pre-world + # tree if it exists. + if [ -d $new ]; then + if [ -z "$preworld" ]; then + panic "New tree should be rotated to old" + fi + if ! remove_tree $new; then + echo "Unable to remove previous pre-world tree." + exit 1 + fi + fi + + if ! mv $NEWTREE $new >&3 2>&1; then + echo "Unable to rename current tree." + exit 1 + fi + fi +} + +# Determine which command we are executing. A command may be +# specified as the first word. If one is not specified then 'update' +# is assumed as the default command. +command="update" +if [ $# -gt 0 ]; then + case "$1" in + build|diff|extract|status|resolve|revert) + command="$1" + shift + ;; + -*) + # If first arg is an option, assume the + # default command. + ;; + *) + usage + ;; + esac +fi + +# Set default variable values. + +# The path to the source tree used to build trees. +SRCDIR=/usr/src + +# The destination directory where the modified files live. +DESTDIR= + +# Ignore changes in the FreeBSD ID string. +FREEBSD_ID= + +# Files that should always have the new version of the file installed. +ALWAYS_INSTALL= + +# Files to ignore and never update during a merge. +IGNORE_FILES= + +# The path to the make binary +MAKE_CMD=make + +# Flags to pass to 'make' when building a tree. +MAKE_OPTIONS= + +# Include a config file if it exists. Note that command line options +# override any settings in the config file. More details are in the +# manual, but in general the following variables can be set: +# - ALWAYS_INSTALL +# - DESTDIR +# - EDITOR +# - FREEBSD_ID +# - IGNORE_FILES +# - LOGFILE +# - MAKE_CMD +# - MAKE_OPTIONS +# - SRCDIR +# - WORKDIR +if [ -r /etc/etcupdate.conf ]; then + . /etc/etcupdate.conf +fi + +# Parse command line options +tarball= +rerun= +always= +dryrun= +ignore= +nobuild= +preworld= +noroot= +while getopts "d:m:nprs:t:A:BD:FI:L:M:N" option; do + case "$option" in + d) + WORKDIR=$OPTARG + ;; + m) + MAKE_CMD=$OPTARG + ;; + n) + dryrun=YES + ;; + p) + preworld=YES + ;; + r) + rerun=YES + ;; + s) + SRCDIR=$OPTARG + ;; + t) + tarball=$OPTARG + ;; + A) + # To allow this option to be specified + # multiple times, accumulate command-line + # specified patterns in an 'always' variable + # and use that to overwrite ALWAYS_INSTALL + # after parsing all options. Need to be + # careful here with globbing expansion. + set -o noglob + always="$always $OPTARG" + set +o noglob + ;; + B) + nobuild=YES + ;; + D) + DESTDIR=$OPTARG + ;; + F) + FREEBSD_ID=YES + ;; + I) + # To allow this option to be specified + # multiple times, accumulate command-line + # specified patterns in an 'ignore' variable + # and use that to overwrite IGNORE_FILES after + # parsing all options. Need to be careful + # here with globbing expansion. + set -o noglob + ignore="$ignore $OPTARG" + set +o noglob + ;; + L) + LOGFILE=$OPTARG + ;; + M) + MAKE_OPTIONS="$OPTARG" + ;; + N) + noroot=YES + ;; + *) + echo + usage + ;; + esac +done +shift $((OPTIND - 1)) + +# Allow -A command line options to override ALWAYS_INSTALL set from +# the config file. +set -o noglob +if [ -n "$always" ]; then + ALWAYS_INSTALL="$always" +fi + +# Allow -I command line options to override IGNORE_FILES set from the +# config file. +if [ -n "$ignore" ]; then + IGNORE_FILES="$ignore" +fi +set +o noglob + +# Where the "old" and "new" trees are stored. +WORKDIR=${WORKDIR:-$DESTDIR/var/db/etcupdate} + +# Log file for verbose output from program that are run. The log file +# is opened on fd '3'. +LOGFILE=${LOGFILE:-$WORKDIR/log} + +# The path of the "old" tree +OLDTREE=$WORKDIR/old + +# The path of the "new" tree +NEWTREE=$WORKDIR/current + +# The path of the "conflicts" tree where files with merge conflicts are saved. +CONFLICTS=$WORKDIR/conflicts + +# The path of the "warnings" file that accumulates warning notes from an update. +WARNINGS=$WORKDIR/warnings + +# Use $EDITOR for resolving conflicts. If it is not set, default to vi. +EDITOR=${EDITOR:-/usr/bin/vi} + +# Files that need to be updated before installworld. +PREWORLD_FILES="etc/master.passwd etc/group" + +# Handle command-specific argument processing such as complaining +# about unsupported options. Since the configuration file is always +# included, do not complain about extra command line arguments that +# may have been set via the config file rather than the command line. +case $command in + update) + if [ -n "$rerun" -a -n "$tarball" ]; then + echo "Only one of -r or -t can be specified." + echo + usage + fi + if [ -n "$rerun" -a -n "$preworld" ]; then + echo "Only one of -p or -r can be specified." + echo + usage + fi + ;; + build|diff|status|revert) + if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" -o \ + -n "$preworld" ]; then + usage + fi + ;; + resolve) + if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" ]; then + usage + fi + ;; + extract) + if [ -n "$dryrun" -o -n "$rerun" -o -n "$preworld" ]; then + usage + fi + ;; +esac + +# Pre-world mode uses a different set of trees. It leaves the current +# tree as-is so it is still present for a full etcupdate run after the +# world install is complete. Instead, it installs a few critical files +# into a separate tree. +if [ -n "$preworld" ]; then + OLDTREE=$NEWTREE + NEWTREE=$WORKDIR/preworld +fi + +# Open the log file. Don't truncate it if doing a minor operation so +# that a minor operation doesn't lose log info from a major operation. +if ! mkdir -p $WORKDIR 2>/dev/null; then + echo "Failed to create work directory $WORKDIR" +fi + +case $command in + diff|resolve|revert|status) + exec 3>>$LOGFILE + ;; + *) + exec 3>$LOGFILE + ;; +esac + +${command}_cmd "$@" diff --git a/conf/system/include/14.3/makewhatis b/conf/system/include/14.3/makewhatis old mode 100755 new mode 100644 index 6fe3ca8..10e74c4 Binary files a/conf/system/include/14.3/makewhatis and b/conf/system/include/14.3/makewhatis differ diff --git a/conf/system/include/14.3/pfctl b/conf/system/include/14.3/pfctl old mode 100755 new mode 100644 index 475c7a8..226bc7f Binary files a/conf/system/include/14.3/pfctl and b/conf/system/include/14.3/pfctl differ diff --git a/conf/system/include/14.3/pfilctl b/conf/system/include/14.3/pfilctl old mode 100755 new mode 100644 index 5011f3a..264de1c Binary files a/conf/system/include/14.3/pfilctl and b/conf/system/include/14.3/pfilctl differ diff --git a/conf/system/include/14.3/pflogd b/conf/system/include/14.3/pflogd old mode 100755 new mode 100644 index fb6e935..d4927c1 Binary files a/conf/system/include/14.3/pflogd and b/conf/system/include/14.3/pflogd differ diff --git a/conf/system/include/14.3/setfib b/conf/system/include/14.3/setfib old mode 100755 new mode 100644 index 9111e41..3265584 Binary files a/conf/system/include/14.3/setfib and b/conf/system/include/14.3/setfib differ diff --git a/conf/system/include/14.3/sum b/conf/system/include/14.3/sum old mode 100755 new mode 100644 index 5179839..fcc62cd Binary files a/conf/system/include/14.3/sum and b/conf/system/include/14.3/sum differ diff --git a/gui/bastille_manager_util.php b/gui/bastille_manager_util.php index 529285b..80505b4 100644 --- a/gui/bastille_manager_util.php +++ b/gui/bastille_manager_util.php @@ -654,8 +654,9 @@ $document->render(); html_checkbox2('automount',gettext('Auto-mount Nullfs'),!empty($pconfig['automount']) ? true : false,gettext('Auto-mount the nullfs mountpoint if the container is already running.'),'',true); html_checkbox2('createdir',gettext('Create Target Directory'),!empty($pconfig['createdir']) ? true : true,gettext('Create target directory if missing (recommended).'),'',true); if ($is_thinjail): - html_checkbox2('update_base',gettext('Base update confirm'),!empty($pconfig['update_base']) ? true : false,gettext('This is a thin container, therefore the base release will be updated, this affects child containers.'),'',true); - html_checkbox2('update_base_force',gettext('Base update force confirm:'),!empty($pconfig['update_base']) ? true : false,gettext('This will perform a forced base update, this affects child containers.'),'',true); + //html_checkbox2('update_base',gettext('Base update confirm'),!empty($pconfig['update_base']) ? true : false,gettext('This is a thin container, therefore the base release will be updated, this affects child containers.'),'',true); + //html_checkbox2('update_base_force',gettext('Base update force confirm:'),!empty($pconfig['update_base']) ? true : false,gettext('This will perform a forced base update, this affects child containers.'),'',true); + html_text2('update_base',gettext('Container Update'),htmlspecialchars("This is a thin container, the host is missing some core components to manage updates on this containers, therefore this containers has to be manually upgraded from the command-line.")); else: html_checkbox2('update_jail',gettext('Container update confirm:'),!empty($pconfig['update_jail']) ? true : false,gettext('This is a thick container, therefore the update will be performed within its root, current containers are not affected.'),'',true); html_checkbox2('update_jail_force',gettext('Container update force confirm:'),!empty($pconfig['update_jail']) ? true : false,gettext('This will perform a forced jail update, current containers are not affected.'),'',true); diff --git a/version b/version index 95a7a34..8060c02 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.2.24 +1.2.25