Add a pre-world mode of updating similar to the -p option that can be

passed to mergemaster.  In this mode, only changes to /etc/master.passwd
and /etc/group are merged to /etc.  In addition, it uses a temporary
tree to stage these changes rather than overwriting the existing
'current' and 'previous' trees so that a full update can be run after
a normal installworld has completed.

MFC after:	2 weeks
This commit is contained in:
John Baldwin 2013-11-12 19:44:18 +00:00
parent 36da5199bb
commit 21d1f635ee
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=258066
3 changed files with 352 additions and 19 deletions

View File

@ -0,0 +1,238 @@
#!/bin/sh
#
# Copyright (c) 2013 Advanced Computing Technologies LLC
# Written by: John H. Baldwin <jhb@FreeBSD.org>
# 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.
#
# $FreeBSD$
# Regression tests for the pre-world (-p) mode
WORKDIR=work
usage()
{
echo "Usage: preworld.sh [-s script] [-w workdir]"
exit 1
}
# Allow the user to specify an alternate work directory or script.
COMMAND=etcupdate
while getopts "s:w:" option; do
case $option in
s)
COMMAND="sh $OPTARG"
;;
w)
WORKDIR=$OPTARG
;;
*)
echo
usage
;;
esac
done
shift $((OPTIND - 1))
if [ $# -ne 0 ]; then
usage
fi
CONFLICTS=$WORKDIR/conflicts
SRC=$WORKDIR/src
OLD=$WORKDIR/current
TEST=$WORKDIR/test
build_trees()
{
# Populate trees with pre-world files and an additional file
# that should not be touched.
rm -rf $SRC $OLD $TEST $CONFLICTS
# Create the "old" source tree as the starting point
mkdir -p $OLD/etc
cat >> $OLD/etc/master.passwd <<EOF
#
root::0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5::0:0:System &:/:/usr/sbin/nologin
_dhcp:*:65:65::0:0:dhcp programs:/var/empty:/usr/sbin/nologin
uucp:*:66:66::0:0:UUCP pseudo-user:/var/spool/uucppublic:/usr/local/libexec/uucp/uucico
pop:*:68:6::0:0:Post Office Owner:/nonexistent:/usr/sbin/nologin
www:*:80:80::0:0:World Wide Web Owner:/nonexistent:/usr/sbin/nologin
hast:*:845:845::0:0:HAST unprivileged user:/var/empty:/usr/sbin/nologin
nobody:*:65534:65534::0:0:Unprivileged user:/nonexistent:/usr/sbin/nologin
EOF
cat >> $OLD/etc/group <<EOF
#
wheel:*:0:root
daemon:*:1:
kmem:*:2:
sys:*:3:
tty:*:4:
operator:*:5:root
_dhcp:*:65:
uucp:*:66:
dialer:*:68:
network:*:69:
www:*:80:
hast:*:845:
nogroup:*:65533:
nobody:*:65534:
EOF
cat >> $OLD/etc/inetd.conf <<EOF
# Yet another file
EOF
# Copy the "old" source tree to the test tree and make local
# modifications.
cp -R $OLD $TEST
sed -I "" -e 's/root::/root:<rpass>:/' $TEST/etc/master.passwd
cat >> $TEST/etc/master.passwd <<EOF
john:<password>:1001:1001::0:0:John Baldwin:/home/john:/bin/tcsh
messagebus:*:556:556::0:0:D-BUS Daemon User:/nonexistent:/usr/sbin/nologin
polkit:*:562:562::0:0:PolicyKit User:/nonexistent:/usr/sbin/nologin
haldaemon:*:560:560::0:0:HAL Daemon User:/nonexistent:/usr/sbin/nologin
EOF
awk '/wheel/ { printf "%s,john\n", $0; next } // { print }' \
$OLD/etc/group > $TEST/etc/group
cat >> $TEST/etc/group <<EOF
john:*:1001:
messagebus:*:556:
polkit:*:562:
haldaemon:*:560:
EOF
rm $TEST/etc/inetd.conf
# Copy the "old" source tree to the new source tree and
# make upstream modifications.
cp -R $OLD $SRC
sed -I "" -e '/:80:/i\
auditdistd:*:78:77::0:0:Auditdistd unprivileged user:/var/empty:/usr/sbin/nologin' \
$SRC/etc/master.passwd
sed -I "" -e '/:80:/i\
audit:*:77:' \
$SRC/etc/group
cat >> $SRC/etc/inetd.conf <<EOF
# Making this larger
EOF
}
# $1 - relative path to file that should be missing from TEST
missing()
{
if [ -e $TEST/$1 -o -L $TEST/$1 ]; then
echo "File $1 should be missing"
fi
}
# $1 - relative path to file that should be present in TEST
present()
{
if ! [ -e $TEST/$1 -o -L $TEST/$1 ]; then
echo "File $1 should be present"
fi
}
# $1 - relative path to regular file that should be present in TEST
# $2 - optional string that should match file contents
# $3 - optional MD5 of the flie contents, overrides $2 if present
file()
{
local contents sum
if ! [ -f $TEST/$1 ]; then
echo "File $1 should be a regular file"
elif [ $# -eq 2 ]; then
contents=`cat $TEST/$1`
if [ "$contents" != "$2" ]; then
echo "File $1 has wrong contents"
fi
elif [ $# -eq 3 ]; then
sum=`md5 -q $TEST/$1`
if [ "$sum" != "$3" ]; then
echo "File $1 has wrong contents"
fi
fi
}
# $1 - relative path to a regular file that should have a conflict
# $2 - optional MD5 of the conflict file contents
conflict()
{
local sum
if ! [ -f $CONFLICTS/$1 ]; then
echo "File $1 missing conflict"
elif [ $# -gt 1 ]; then
sum=`md5 -q $CONFLICTS/$1`
if [ "$sum" != "$2" ]; then
echo "Conflict $1 has wrong contents"
fi
fi
}
check_trees()
{
echo "Checking tree for correct results:"
file /etc/master.passwd "" 1385366e8b424d33d59b7d8a2bdb15d3
file /etc/group "" 21273f845f6ec0cda9188c4ddac9ed47
missing /etc/inetd.conf
# These should be auto-generated by pwd_mkdb
file /etc/passwd "" 9831537874bdc99adccaa2b0293248a1
file /etc/pwd.db
file /etc/spwd.db
}
if [ `id -u` -ne 0 ]; then
echo "must be root"
fi
if [ -r /etc/etcupdate.conf ]; then
echo "WARNING: /etc/etcupdate.conf settings may break some tests."
fi
build_trees
$COMMAND -np -s $SRC -d $WORKDIR -D $TEST > $WORKDIR/testn.out
cat > $WORKDIR/correct.out <<EOF
M /etc/group
M /etc/master.passwd
EOF
echo "Differences for -n:"
diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/testn.out
$COMMAND -p -s $SRC -d $WORKDIR -D $TEST > $WORKDIR/test.out
echo "Differences for real:"
diff -u -L "correct" $WORKDIR/correct.out -L "test" $WORKDIR/test.out
check_trees

View File

@ -25,7 +25,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd March 16, 2012 .Dd November 12, 2013
.Dt ETCUPDATE 8 .Dt ETCUPDATE 8
.Os .Os
.Sh NAME .Sh NAME
@ -33,7 +33,7 @@
.Nd "manage updates to system files not updated by installworld" .Nd "manage updates to system files not updated by installworld"
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl nBF .Op Fl npBF
.Op Fl d Ar workdir .Op Fl d Ar workdir
.Op Fl r | Fl s Ar source | Fl t Ar tarball .Op Fl r | Fl s Ar source | Fl t Ar tarball
.Op Fl A Ar patterns .Op Fl A Ar patterns
@ -64,6 +64,7 @@
.Op Fl M Ar options .Op Fl M Ar options
.Nm .Nm
.Cm resolve .Cm resolve
.Op Fl p
.Op Fl d Ar workdir .Op Fl d Ar workdir
.Op Fl D Ar destdir .Op Fl D Ar destdir
.Op Fl L Ar logfile .Op Fl L Ar logfile
@ -481,6 +482,33 @@ option is not specified,
then a temporary then a temporary
.Dq current .Dq current
tree will be extracted to perform the comparison. tree will be extracted to perform the comparison.
.It Fl p
Enable
.Dq pre-world
mode.
Only merge changes to files that are necessary to successfully run
.Sq make installworld
or
.Sq make installkernel .
When this flag is enabled,
the existing
.Dq current
and
.Dq previous
trees are left alone.
Instead,
a temporary tree is populated with the necessary files.
This temporary tree is compared against the
.Dq current
tree.
This allows a normal update to be run after
.Sq make installworld
has completed.
Any conflicts generated during a
.Dq pre-world
update should be resolved by a
.Dq pre-world
.Cm resolve .
.It Fl r .It Fl r
Do not update the Do not update the
.Dq current .Dq current

View File

@ -61,14 +61,15 @@
usage() usage()
{ {
cat <<EOF cat <<EOF
usage: etcupdate [-nBF] [-d workdir] [-r | -s source | -t tarball] [-A patterns] usage: etcupdate [-npBF] [-d workdir] [-r | -s source | -t tarball]
[-D destdir] [-I patterns] [-L logfile] [-M options] [-A patterns] [-D destdir] [-I patterns] [-L logfile]
[-M options]
etcupdate build [-B] [-d workdir] [-s source] [-L logfile] [-M options] etcupdate build [-B] [-d workdir] [-s source] [-L logfile] [-M options]
<tarball> <tarball>
etcupdate diff [-d workdir] [-D destdir] [-I patterns] [-L logfile] etcupdate diff [-d workdir] [-D destdir] [-I patterns] [-L logfile]
etcupdate extract [-B] [-d workdir] [-s source | -t tarball] [-L logfile] etcupdate extract [-B] [-d workdir] [-s source | -t tarball] [-L logfile]
[-M options] [-M options]
etcupdate resolve [-d workdir] [-D destdir] [-L logfile] etcupdate resolve [-p] [-d workdir] [-D destdir] [-L logfile]
etcupdate status [-d workdir] [-D destdir] etcupdate status [-d workdir] [-D destdir]
EOF EOF
exit 1 exit 1
@ -181,22 +182,31 @@ always_install()
# $1 - directory to store new tree in # $1 - directory to store new tree in
build_tree() build_tree()
{ {
local make local destdir dir file make
make="make $MAKE_OPTIONS" make="make $MAKE_OPTIONS"
log "Building tree at $1 with $make" log "Building tree at $1 with $make"
mkdir -p $1/usr/obj >&3 2>&1 mkdir -p $1/usr/obj >&3 2>&1
(cd $SRCDIR; $make DESTDIR=$1 distrib-dirs) >&3 2>&1 || return 1 destdir=`realpath $1`
if ! [ -n "$nobuild" ]; then if [ -n "$preworld" ]; then
(cd $SRCDIR; \ # Build a limited tree that only contains files that are
MAKEOBJDIRPREFIX=$1/usr/obj $make _obj SUBDIR_OVERRIDE=etc && # crucial to installworld.
MAKEOBJDIRPREFIX=$1/usr/obj $make everything SUBDIR_OVERRIDE=etc && for file in $PREWORLD_FILES; do
MAKEOBJDIRPREFIX=$1/usr/obj $make DESTDIR=$1 distribution) \ dir=`dirname /$file`
mkdir -p $1/$dir >&3 2>&1 || return 1
cp -p $SRCDIR/$file $1/$file || return 1
done
elif ! [ -n "$nobuild" ]; then
(cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs &&
MAKEOBJDIRPREFIX=$destdir/usr/obj $make _obj SUBDIR_OVERRIDE=etc &&
MAKEOBJDIRPREFIX=$destdir/usr/obj $make everything SUBDIR_OVERRIDE=etc &&
MAKEOBJDIRPREFIX=$destdir/usr/obj $make DESTDIR=$destdir distribution) \
>&3 2>&1 || return 1 >&3 2>&1 || return 1
else else
(cd $SRCDIR; $make DESTDIR=$1 distribution) >&3 2>&1 || return 1 (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs &&
$make DESTDIR=$destdir distribution) >&3 2>&1 || return 1
fi fi
chflags -R noschg $1 >&3 2>&1 || return 1 chflags -R noschg $1 >&3 2>&1 || return 1
rm -rf $1/usr/obj >&3 2>&1 || return 1 rm -rf $1/usr/obj >&3 2>&1 || return 1
@ -218,9 +228,15 @@ build_tree()
# source tree. # source tree.
extract_tree() extract_tree()
{ {
local files
# If we have a tarball, extract that into the new directory. # If we have a tarball, extract that into the new directory.
if [ -n "$tarball" ]; then if [ -n "$tarball" ]; then
if ! (mkdir -p $NEWTREE && tar xf $tarball -C $NEWTREE) \ files=
if [ -n "$preworld" ]; then
files="$PREWORLD_FILES"
fi
if ! (mkdir -p $NEWTREE && tar xf $tarball -C $NEWTREE $files) \
>&3 2>&1; then >&3 2>&1; then
echo "Failed to extract new tree." echo "Failed to extract new tree."
remove_tree $NEWTREE remove_tree $NEWTREE
@ -1298,6 +1314,11 @@ resolve_cmd()
return return
fi 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/^\.//'` conflicts=`(cd $CONFLICTS; find . ! -type d) | sed -e 's/^\.//'`
for file in $conflicts; do for file in $conflicts; do
resolve_conflict $file resolve_conflict $file
@ -1343,7 +1364,7 @@ update_cmd()
usage usage
fi fi
log "update command: rerun=$rerun tarball=$tarball" log "update command: rerun=$rerun tarball=$tarball preworld=$preworld"
if [ `id -u` -ne 0 ]; then if [ `id -u` -ne 0 ]; then
echo "Must be root to update a tree." echo "Must be root to update a tree."
@ -1376,9 +1397,22 @@ update_cmd()
echo "Unable to create temporary directory." echo "Unable to create temporary directory."
exit 1 exit 1
fi fi
OLDTREE=$NEWTREE
# A pre-world dryrun has already set OLDTREE to
# point to the current stock tree.
if [ -z "$preworld" ]; then
OLDTREE=$NEWTREE
fi
NEWTREE=$dir NEWTREE=$dir
# For a pre-world update, blow away any pre-existing
# NEWTREE.
elif [ -n "$preworld" ]; then
if ! remove_tree $NEWTREE; then
echo "Unable to remove pre-world tree."
exit 1
fi
# Rotate the existing stock tree to the old tree. # Rotate the existing stock tree to the old tree.
elif [ -d $NEWTREE ]; then elif [ -d $NEWTREE ]; then
# First, delete the previous old tree if it exists. # First, delete the previous old tree if it exists.
@ -1421,6 +1455,12 @@ EOF
# Initialize conflicts and warnings handling. # Initialize conflicts and warnings handling.
rm -f $WARNINGS rm -f $WARNINGS
mkdir -p $CONFLICTS mkdir -p $CONFLICTS
# 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 # The order for the following sections is important. In the
# odd case that a directory is converted into a file, the # odd case that a directory is converted into a file, the
@ -1535,7 +1575,8 @@ always=
dryrun= dryrun=
ignore= ignore=
nobuild= nobuild=
while getopts "d:nrs:t:A:BD:FI:L:M:" option; do preworld=
while getopts "d:nprs:t:A:BD:FI:L:M:" option; do
case "$option" in case "$option" in
d) d)
WORKDIR=$OPTARG WORKDIR=$OPTARG
@ -1543,6 +1584,9 @@ while getopts "d:nrs:t:A:BD:FI:L:M:" option; do
n) n)
dryrun=YES dryrun=YES
;; ;;
p)
preworld=YES
;;
r) r)
rerun=YES rerun=YES
;; ;;
@ -1633,6 +1677,9 @@ WARNINGS=$WORKDIR/warnings
# Use $EDITOR for resolving conflicts. If it is not set, default to vi. # Use $EDITOR for resolving conflicts. If it is not set, default to vi.
EDITOR=${EDITOR:-/usr/bin/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 # Handle command-specific argument processing such as complaining
# about unsupported options. Since the configuration file is always # about unsupported options. Since the configuration file is always
# included, do not complain about extra command line arguments that # included, do not complain about extra command line arguments that
@ -1644,19 +1691,39 @@ case $command in
echo echo
usage usage
fi fi
if [ -n "$rerun" -a -n "$preworld" ]; then
echo "Only one of -p or -r can be specified."
echo
usage
fi
;; ;;
build|diff|resolve|status) build|diff|status)
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 if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" ]; then
usage usage
fi fi
;; ;;
extract) extract)
if [ -n "$dryrun" -o -n "$rerun" ]; then if [ -n "$dryrun" -o -n "$rerun" -o -n "$preworld" ]; then
usage usage
fi fi
;; ;;
esac 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 # 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. # that a minor operation doesn't lose log info from a major operation.
if ! mkdir -p $WORKDIR 2>/dev/null; then if ! mkdir -p $WORKDIR 2>/dev/null; then