1238423Sjhb#!/bin/sh 2238423Sjhb# 3283927Sjhb# Copyright (c) 2010-2013 Hudson River Trading LLC 4238423Sjhb# Written by: John H. Baldwin <jhb@FreeBSD.org> 5238423Sjhb# All rights reserved. 6238423Sjhb# 7238423Sjhb# Redistribution and use in source and binary forms, with or without 8238423Sjhb# modification, are permitted provided that the following conditions 9238423Sjhb# are met: 10238423Sjhb# 1. Redistributions of source code must retain the above copyright 11238423Sjhb# notice, this list of conditions and the following disclaimer. 12238423Sjhb# 2. Redistributions in binary form must reproduce the above copyright 13238423Sjhb# notice, this list of conditions and the following disclaimer in the 14238423Sjhb# documentation and/or other materials provided with the distribution. 15238423Sjhb# 16238423Sjhb# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17238423Sjhb# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18238423Sjhb# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19238423Sjhb# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20238423Sjhb# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21238423Sjhb# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22238423Sjhb# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23238423Sjhb# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24238423Sjhb# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25238423Sjhb# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26238423Sjhb# SUCH DAMAGE. 27238423Sjhb# 28238423Sjhb# $FreeBSD$ 29238423Sjhb 30238423Sjhb# This is a tool to manage updating files that are not updated as part 31238423Sjhb# of 'make installworld' such as files in /etc. Unlike other tools, 32238423Sjhb# this one is specifically tailored to assisting with mass upgrades. 33238423Sjhb# To that end it does not require user intervention while running. 34238423Sjhb# 35238423Sjhb# Theory of operation: 36238423Sjhb# 37238423Sjhb# The most reliable way to update changes to files that have local 38238423Sjhb# modifications is to perform a three-way merge between the original 39238423Sjhb# unmodified file, the new version of the file, and the modified file. 40238423Sjhb# This requires having all three versions of the file available when 41238423Sjhb# performing an update. 42238423Sjhb# 43238423Sjhb# To that end, etcupdate uses a strategy where the current unmodified 44238423Sjhb# tree is kept in WORKDIR/current and the previous unmodified tree is 45238423Sjhb# kept in WORKDIR/old. When performing a merge, a new tree is built 46238423Sjhb# if needed and then the changes are merged into DESTDIR. Any files 47238423Sjhb# with unresolved conflicts after the merge are left in a tree rooted 48238423Sjhb# at WORKDIR/conflicts. 49238423Sjhb# 50238423Sjhb# To provide extra flexibility, etcupdate can also build tarballs of 51238423Sjhb# root trees that can later be used. It can also use a tarball as the 52238423Sjhb# source of a new tree instead of building it from /usr/src. 53238423Sjhb 54238423Sjhb# Global settings. These can be adjusted by config files and in some 55238423Sjhb# cases by command line options. 56238423Sjhb 57238423Sjhb# TODO: 58238423Sjhb# - automatable conflict resolution 59238423Sjhb# - a 'revert' command to make a file "stock" 60238423Sjhb 61238423Sjhbusage() 62238423Sjhb{ 63238423Sjhb cat <<EOF 64259960Sjhbusage: etcupdate [-npBF] [-d workdir] [-r | -s source | -t tarball] 65259960Sjhb [-A patterns] [-D destdir] [-I patterns] [-L logfile] 66259960Sjhb [-M options] 67238423Sjhb etcupdate build [-B] [-d workdir] [-s source] [-L logfile] [-M options] 68238423Sjhb <tarball> 69238423Sjhb etcupdate diff [-d workdir] [-D destdir] [-I patterns] [-L logfile] 70238423Sjhb etcupdate extract [-B] [-d workdir] [-s source | -t tarball] [-L logfile] 71238423Sjhb [-M options] 72259960Sjhb etcupdate resolve [-p] [-d workdir] [-D destdir] [-L logfile] 73238423Sjhb etcupdate status [-d workdir] [-D destdir] 74238423SjhbEOF 75238423Sjhb exit 1 76238423Sjhb} 77238423Sjhb 78238423Sjhb# Used to write a message prepended with '>>>' to the logfile. 79238423Sjhblog() 80238423Sjhb{ 81238423Sjhb echo ">>>" "$@" >&3 82238423Sjhb} 83238423Sjhb 84238423Sjhb# Used for assertion conditions that should never happen. 85238423Sjhbpanic() 86238423Sjhb{ 87238423Sjhb echo "PANIC:" "$@" 88238423Sjhb exit 10 89238423Sjhb} 90238423Sjhb 91238423Sjhb# Used to write a warning message. These are saved to the WARNINGS 92238423Sjhb# file with " " prepended. 93238423Sjhbwarn() 94238423Sjhb{ 95238423Sjhb echo -n " " >> $WARNINGS 96238423Sjhb echo "$@" >> $WARNINGS 97238423Sjhb} 98238423Sjhb 99238423Sjhb# Output a horizontal rule using the passed-in character. Matches the 100238423Sjhb# length used for Index lines in CVS and SVN diffs. 101238423Sjhb# 102238423Sjhb# $1 - character 103238423Sjhbrule() 104238423Sjhb{ 105238423Sjhb jot -b "$1" -s "" 67 106238423Sjhb} 107238423Sjhb 108238423Sjhb# Output a text description of a specified file's type. 109238423Sjhb# 110238423Sjhb# $1 - file pathname. 111238423Sjhbfile_type() 112238423Sjhb{ 113238423Sjhb stat -f "%HT" $1 | tr "[:upper:]" "[:lower:]" 114238423Sjhb} 115238423Sjhb 116238423Sjhb# Returns true (0) if a file exists 117238423Sjhb# 118238423Sjhb# $1 - file pathname. 119238423Sjhbexists() 120238423Sjhb{ 121238423Sjhb [ -e $1 -o -L $1 ] 122238423Sjhb} 123238423Sjhb 124238423Sjhb# Returns true (0) if a file should be ignored, false otherwise. 125238423Sjhb# 126238423Sjhb# $1 - file pathname 127238423Sjhbignore() 128238423Sjhb{ 129238423Sjhb local pattern - 130238423Sjhb 131238423Sjhb set -o noglob 132238423Sjhb for pattern in $IGNORE_FILES; do 133238423Sjhb set +o noglob 134238423Sjhb case $1 in 135238423Sjhb $pattern) 136238423Sjhb return 0 137238423Sjhb ;; 138238423Sjhb esac 139238423Sjhb set -o noglob 140238423Sjhb done 141238423Sjhb 142238423Sjhb # Ignore /.cshrc and /.profile if they are hardlinked to the 143238423Sjhb # same file in /root. This ensures we only compare those 144238423Sjhb # files once in that case. 145238423Sjhb case $1 in 146238423Sjhb /.cshrc|/.profile) 147238423Sjhb if [ ${DESTDIR}$1 -ef ${DESTDIR}/root$1 ]; then 148238423Sjhb return 0 149238423Sjhb fi 150238423Sjhb ;; 151238423Sjhb *) 152238423Sjhb ;; 153238423Sjhb esac 154238423Sjhb 155238423Sjhb return 1 156238423Sjhb} 157238423Sjhb 158238423Sjhb# Returns true (0) if the new version of a file should always be 159238423Sjhb# installed rather than attempting to do a merge. 160238423Sjhb# 161238423Sjhb# $1 - file pathname 162238423Sjhbalways_install() 163238423Sjhb{ 164238423Sjhb local pattern - 165238423Sjhb 166238423Sjhb set -o noglob 167238423Sjhb for pattern in $ALWAYS_INSTALL; do 168238423Sjhb set +o noglob 169238423Sjhb case $1 in 170238423Sjhb $pattern) 171238423Sjhb return 0 172238423Sjhb ;; 173238423Sjhb esac 174238423Sjhb set -o noglob 175238423Sjhb done 176238423Sjhb 177238423Sjhb return 1 178238423Sjhb} 179238423Sjhb 180238423Sjhb# Build a new tree 181238423Sjhb# 182238423Sjhb# $1 - directory to store new tree in 183238423Sjhbbuild_tree() 184238423Sjhb{ 185259960Sjhb local destdir dir file make 186238423Sjhb 187238423Sjhb make="make $MAKE_OPTIONS" 188238423Sjhb 189238423Sjhb log "Building tree at $1 with $make" 190238423Sjhb mkdir -p $1/usr/obj >&3 2>&1 191259960Sjhb destdir=`realpath $1` 192238423Sjhb 193259960Sjhb if [ -n "$preworld" ]; then 194259960Sjhb # Build a limited tree that only contains files that are 195259960Sjhb # crucial to installworld. 196259960Sjhb for file in $PREWORLD_FILES; do 197259960Sjhb dir=`dirname /$file` 198259960Sjhb mkdir -p $1/$dir >&3 2>&1 || return 1 199259960Sjhb cp -p $SRCDIR/$file $1/$file || return 1 200259960Sjhb done 201259960Sjhb elif ! [ -n "$nobuild" ]; then 202259960Sjhb (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs && 203259960Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make _obj SUBDIR_OVERRIDE=etc && 204259960Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make everything SUBDIR_OVERRIDE=etc && 205259960Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make DESTDIR=$destdir distribution) \ 206238423Sjhb >&3 2>&1 || return 1 207238423Sjhb else 208259960Sjhb (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs && 209259960Sjhb $make DESTDIR=$destdir distribution) >&3 2>&1 || return 1 210238423Sjhb fi 211238423Sjhb chflags -R noschg $1 >&3 2>&1 || return 1 212238423Sjhb rm -rf $1/usr/obj >&3 2>&1 || return 1 213238423Sjhb 214238423Sjhb # Purge auto-generated files. Only the source files need to 215238423Sjhb # be updated after which these files are regenerated. 216238423Sjhb rm -f $1/etc/*.db $1/etc/passwd >&3 2>&1 || return 1 217238423Sjhb 218238423Sjhb # Remove empty files. These just clutter the output of 'diff'. 219238423Sjhb find $1 -type f -size 0 -delete >&3 2>&1 || return 1 220238423Sjhb 221238423Sjhb # Trim empty directories. 222238423Sjhb find -d $1 -type d -empty -delete >&3 2>&1 || return 1 223238423Sjhb return 0 224238423Sjhb} 225238423Sjhb 226238423Sjhb# Generate a new NEWTREE tree. If tarball is set, then the tree is 227238423Sjhb# extracted from the tarball. Otherwise the tree is built from a 228238423Sjhb# source tree. 229238423Sjhbextract_tree() 230238423Sjhb{ 231259960Sjhb local files 232259960Sjhb 233238423Sjhb # If we have a tarball, extract that into the new directory. 234238423Sjhb if [ -n "$tarball" ]; then 235259960Sjhb files= 236259960Sjhb if [ -n "$preworld" ]; then 237259960Sjhb files="$PREWORLD_FILES" 238259960Sjhb fi 239259960Sjhb if ! (mkdir -p $NEWTREE && tar xf $tarball -C $NEWTREE $files) \ 240238423Sjhb >&3 2>&1; then 241238423Sjhb echo "Failed to extract new tree." 242238423Sjhb remove_tree $NEWTREE 243238423Sjhb exit 1 244238423Sjhb fi 245238423Sjhb else 246238423Sjhb if ! build_tree $NEWTREE; then 247238423Sjhb echo "Failed to build new tree." 248238423Sjhb remove_tree $NEWTREE 249238423Sjhb exit 1 250238423Sjhb fi 251238423Sjhb fi 252238423Sjhb} 253238423Sjhb 254238423Sjhb# Forcefully remove a tree. Returns true (0) if the operation succeeds. 255238423Sjhb# 256238423Sjhb# $1 - path to tree 257238423Sjhbremove_tree() 258238423Sjhb{ 259238423Sjhb 260238423Sjhb rm -rf $1 >&3 2>&1 261238423Sjhb if [ -e $1 ]; then 262238423Sjhb chflags -R noschg $1 >&3 2>&1 263238423Sjhb rm -rf $1 >&3 2>&1 264238423Sjhb fi 265238423Sjhb [ ! -e $1 ] 266238423Sjhb} 267238423Sjhb 268238423Sjhb# Return values for compare() 269238423SjhbCOMPARE_EQUAL=0 270238423SjhbCOMPARE_ONLYFIRST=1 271238423SjhbCOMPARE_ONLYSECOND=2 272238423SjhbCOMPARE_DIFFTYPE=3 273238423SjhbCOMPARE_DIFFLINKS=4 274238423SjhbCOMPARE_DIFFFILES=5 275238423Sjhb 276238423Sjhb# Compare two files/directories/symlinks. Note that this does not 277238423Sjhb# recurse into subdirectories. Instead, if two nodes are both 278238423Sjhb# directories, they are assumed to be equivalent. 279238423Sjhb# 280238423Sjhb# Returns true (0) if the nodes are identical. If only one of the two 281238423Sjhb# nodes are present, return one of the COMPARE_ONLY* constants. If 282238423Sjhb# the nodes are different, return one of the COMPARE_DIFF* constants 283238423Sjhb# to indicate the type of difference. 284238423Sjhb# 285238423Sjhb# $1 - first node 286238423Sjhb# $2 - second node 287238423Sjhbcompare() 288238423Sjhb{ 289238423Sjhb local first second 290238423Sjhb 291238423Sjhb # If the first node doesn't exist, then check for the second 292238423Sjhb # node. Note that -e will fail for a symbolic link that 293238423Sjhb # points to a missing target. 294238423Sjhb if ! exists $1; then 295238423Sjhb if exists $2; then 296238423Sjhb return $COMPARE_ONLYSECOND 297238423Sjhb else 298238423Sjhb return $COMPARE_EQUAL 299238423Sjhb fi 300238423Sjhb elif ! exists $2; then 301238423Sjhb return $COMPARE_ONLYFIRST 302238423Sjhb fi 303238423Sjhb 304238423Sjhb # If the two nodes are different file types fail. 305238423Sjhb first=`stat -f "%Hp" $1` 306238423Sjhb second=`stat -f "%Hp" $2` 307238423Sjhb if [ "$first" != "$second" ]; then 308238423Sjhb return $COMPARE_DIFFTYPE 309238423Sjhb fi 310238423Sjhb 311238423Sjhb # If both are symlinks, compare the link values. 312238423Sjhb if [ -L $1 ]; then 313238423Sjhb first=`readlink $1` 314238423Sjhb second=`readlink $2` 315238423Sjhb if [ "$first" = "$second" ]; then 316238423Sjhb return $COMPARE_EQUAL 317238423Sjhb else 318238423Sjhb return $COMPARE_DIFFLINKS 319238423Sjhb fi 320238423Sjhb fi 321238423Sjhb 322238423Sjhb # If both are files, compare the file contents. 323238423Sjhb if [ -f $1 ]; then 324238423Sjhb if cmp -s $1 $2; then 325238423Sjhb return $COMPARE_EQUAL 326238423Sjhb else 327238423Sjhb return $COMPARE_DIFFFILES 328238423Sjhb fi 329238423Sjhb fi 330238423Sjhb 331238423Sjhb # As long as the two nodes are the same type of file, consider 332238423Sjhb # them equivalent. 333238423Sjhb return $COMPARE_EQUAL 334238423Sjhb} 335238423Sjhb 336238423Sjhb# Returns true (0) if the only difference between two regular files is a 337238423Sjhb# change in the FreeBSD ID string. 338238423Sjhb# 339238423Sjhb# $1 - path of first file 340238423Sjhb# $2 - path of second file 341238423Sjhbfbsdid_only() 342238423Sjhb{ 343238423Sjhb 344238423Sjhb diff -qI '\$FreeBSD.*\$' $1 $2 >/dev/null 2>&1 345238423Sjhb} 346238423Sjhb 347238423Sjhb# This is a wrapper around compare that will return COMPARE_EQUAL if 348238423Sjhb# the only difference between two regular files is a change in the 349238423Sjhb# FreeBSD ID string. It only makes this adjustment if the -F flag has 350238423Sjhb# been specified. 351238423Sjhb# 352238423Sjhb# $1 - first node 353238423Sjhb# $2 - second node 354238423Sjhbcompare_fbsdid() 355238423Sjhb{ 356238423Sjhb local cmp 357238423Sjhb 358238423Sjhb compare $1 $2 359238423Sjhb cmp=$? 360238423Sjhb 361238423Sjhb if [ -n "$FREEBSD_ID" -a "$cmp" -eq $COMPARE_DIFFFILES ] && \ 362238423Sjhb fbsdid_only $1 $2; then 363238423Sjhb return $COMPARE_EQUAL 364238423Sjhb fi 365238423Sjhb 366238423Sjhb return $cmp 367238423Sjhb} 368238423Sjhb 369238423Sjhb# Returns true (0) if a directory is empty. 370238423Sjhb# 371238423Sjhb# $1 - pathname of the directory to check 372238423Sjhbempty_dir() 373238423Sjhb{ 374238423Sjhb local contents 375238423Sjhb 376238423Sjhb contents=`ls -A $1` 377238423Sjhb [ -z "$contents" ] 378238423Sjhb} 379238423Sjhb 380238423Sjhb# Returns true (0) if one directories contents are a subset of the 381238423Sjhb# other. This will recurse to handle subdirectories and compares 382238423Sjhb# individual files in the trees. Its purpose is to quiet spurious 383238423Sjhb# directory warnings for dryrun invocations. 384238423Sjhb# 385238423Sjhb# $1 - first directory (sub) 386238423Sjhb# $2 - second directory (super) 387238423Sjhbdir_subset() 388238423Sjhb{ 389238423Sjhb local contents file 390238423Sjhb 391238423Sjhb if ! [ -d $1 -a -d $2 ]; then 392238423Sjhb return 1 393238423Sjhb fi 394238423Sjhb 395238423Sjhb # Ignore files that are present in the second directory but not 396238423Sjhb # in the first. 397238423Sjhb contents=`ls -A $1` 398238423Sjhb for file in $contents; do 399238423Sjhb if ! compare $1/$file $2/$file; then 400238423Sjhb return 1 401238423Sjhb fi 402238423Sjhb 403238423Sjhb if [ -d $1/$file ]; then 404238423Sjhb if ! dir_subset $1/$file $2/$file; then 405238423Sjhb return 1 406238423Sjhb fi 407238423Sjhb fi 408238423Sjhb done 409238423Sjhb return 0 410238423Sjhb} 411238423Sjhb 412238423Sjhb# Returns true (0) if a directory in the destination tree is empty. 413238423Sjhb# If this is a dryrun, then this returns true as long as the contents 414238423Sjhb# of the directory are a subset of the contents in the old tree 415238423Sjhb# (meaning that the directory would be empty in a non-dryrun when this 416238423Sjhb# was invoked) to quiet spurious warnings. 417238423Sjhb# 418238423Sjhb# $1 - pathname of the directory to check relative to DESTDIR. 419238423Sjhbempty_destdir() 420238423Sjhb{ 421238423Sjhb 422238423Sjhb if [ -n "$dryrun" ]; then 423238423Sjhb dir_subset $DESTDIR/$1 $OLDTREE/$1 424238423Sjhb return 425238423Sjhb fi 426238423Sjhb 427238423Sjhb empty_dir $DESTDIR/$1 428238423Sjhb} 429238423Sjhb 430238423Sjhb# Output a diff of two directory entries with the same relative name 431238423Sjhb# in different trees. Note that as with compare(), this does not 432238423Sjhb# recurse into subdirectories. If the nodes are identical, nothing is 433238423Sjhb# output. 434238423Sjhb# 435238423Sjhb# $1 - first tree 436238423Sjhb# $2 - second tree 437238423Sjhb# $3 - node name 438238423Sjhb# $4 - label for first tree 439238423Sjhb# $5 - label for second tree 440238423Sjhbdiffnode() 441238423Sjhb{ 442238423Sjhb local first second file old new diffargs 443238423Sjhb 444238423Sjhb if [ -n "$FREEBSD_ID" ]; then 445238423Sjhb diffargs="-I \\\$FreeBSD.*\\\$" 446238423Sjhb else 447238423Sjhb diffargs="" 448238423Sjhb fi 449238423Sjhb 450238423Sjhb compare_fbsdid $1/$3 $2/$3 451238423Sjhb case $? in 452238423Sjhb $COMPARE_EQUAL) 453238423Sjhb ;; 454238423Sjhb $COMPARE_ONLYFIRST) 455238423Sjhb echo 456238423Sjhb echo "Removed: $3" 457238423Sjhb echo 458238423Sjhb ;; 459238423Sjhb $COMPARE_ONLYSECOND) 460238423Sjhb echo 461238423Sjhb echo "Added: $3" 462238423Sjhb echo 463238423Sjhb ;; 464238423Sjhb $COMPARE_DIFFTYPE) 465238423Sjhb first=`file_type $1/$3` 466238423Sjhb second=`file_type $2/$3` 467238423Sjhb echo 468238423Sjhb echo "Node changed from a $first to a $second: $3" 469238423Sjhb echo 470238423Sjhb ;; 471238423Sjhb $COMPARE_DIFFLINKS) 472238423Sjhb first=`readlink $1/$file` 473238423Sjhb second=`readlink $2/$file` 474238423Sjhb echo 475238423Sjhb echo "Link changed: $file" 476238423Sjhb rule "=" 477238423Sjhb echo "-$first" 478238423Sjhb echo "+$second" 479238423Sjhb echo 480238423Sjhb ;; 481238423Sjhb $COMPARE_DIFFFILES) 482238423Sjhb echo "Index: $3" 483238423Sjhb rule "=" 484238423Sjhb diff -u $diffargs -L "$3 ($4)" $1/$3 -L "$3 ($5)" $2/$3 485238423Sjhb ;; 486238423Sjhb esac 487238423Sjhb} 488238423Sjhb 489259960Sjhb# Run one-off commands after an update has completed. These commands 490259960Sjhb# are not tied to a specific file, so they cannot be handled by 491259960Sjhb# post_install_file(). 492259960Sjhbpost_update() 493259960Sjhb{ 494259960Sjhb local args 495259960Sjhb 496259960Sjhb # None of these commands should be run for a pre-world update. 497259960Sjhb if [ -n "$preworld" ]; then 498259960Sjhb return 499259960Sjhb fi 500259960Sjhb 501259960Sjhb # If /etc/localtime exists and is not a symlink and /var/db/zoneinfo 502259960Sjhb # exists, run tzsetup -r to refresh /etc/localtime. 503259960Sjhb if [ -f ${DESTDIR}/etc/localtime -a \ 504259960Sjhb ! -L ${DESTDIR}/etc/localtime ]; then 505259960Sjhb if [ -f ${DESTDIR}/var/db/zoneinfo ]; then 506259960Sjhb if [ -n "${DESTDIR}" ]; then 507259960Sjhb args="-C ${DESTDIR}" 508259960Sjhb else 509259960Sjhb args="" 510259960Sjhb fi 511259960Sjhb log "tzsetup -r ${args}" 512259960Sjhb if [ -z "$dryrun" ]; then 513259960Sjhb tzsetup -r ${args} >&3 2>&1 514259960Sjhb fi 515259960Sjhb else 516259960Sjhb warn "Needs update: /etc/localtime (required" \ 517259960Sjhb "manual update via tzsetup(1))" 518259960Sjhb fi 519259960Sjhb fi 520259960Sjhb} 521259960Sjhb 522238423Sjhb# Create missing parent directories of a node in a target tree 523238423Sjhb# preserving the owner, group, and permissions from a specified 524238423Sjhb# template tree. 525238423Sjhb# 526238423Sjhb# $1 - template tree 527238423Sjhb# $2 - target tree 528238423Sjhb# $3 - pathname of the node (relative to both trees) 529238423Sjhbinstall_dirs() 530238423Sjhb{ 531238423Sjhb local args dir 532238423Sjhb 533238423Sjhb dir=`dirname $3` 534238423Sjhb 535238423Sjhb # Nothing to do if the parent directory exists. This also 536238423Sjhb # catches the degenerate cases when the path is just a simple 537238423Sjhb # filename. 538238423Sjhb if [ -d ${2}$dir ]; then 539238423Sjhb return 0 540238423Sjhb fi 541238423Sjhb 542238423Sjhb # If non-directory file exists with the desired directory 543238423Sjhb # name, then fail. 544238423Sjhb if exists ${2}$dir; then 545238423Sjhb # If this is a dryrun and we are installing the 546238423Sjhb # directory in the DESTDIR and the file in the DESTDIR 547238423Sjhb # matches the file in the old tree, then fake success 548238423Sjhb # to quiet spurious warnings. 549238423Sjhb if [ -n "$dryrun" -a "$2" = "$DESTDIR" ]; then 550238423Sjhb if compare $OLDTREE/$dir $DESTDIR/$dir; then 551238423Sjhb return 0 552238423Sjhb fi 553238423Sjhb fi 554238423Sjhb 555238423Sjhb args=`file_type ${2}$dir` 556238423Sjhb warn "Directory mismatch: ${2}$dir ($args)" 557238423Sjhb return 1 558238423Sjhb fi 559238423Sjhb 560238423Sjhb # Ensure the parent directory of the directory is present 561238423Sjhb # first. 562238423Sjhb if ! install_dirs $1 "$2" $dir; then 563238423Sjhb return 1 564238423Sjhb fi 565238423Sjhb 566238423Sjhb # Format attributes from template directory as install(1) 567238423Sjhb # arguments. 568238423Sjhb args=`stat -f "-o %Su -g %Sg -m %0Mp%0Lp" $1/$dir` 569238423Sjhb 570238423Sjhb log "install -d $args ${2}$dir" 571238423Sjhb if [ -z "$dryrun" ]; then 572238423Sjhb install -d $args ${2}$dir >&3 2>&1 573238423Sjhb fi 574238423Sjhb return 0 575238423Sjhb} 576238423Sjhb 577238423Sjhb# Perform post-install fixups for a file. This largely consists of 578238423Sjhb# regenerating any files that depend on the newly installed file. 579238423Sjhb# 580238423Sjhb# $1 - pathname of the updated file (relative to DESTDIR) 581238423Sjhbpost_install_file() 582238423Sjhb{ 583238423Sjhb case $1 in 584238423Sjhb /etc/mail/aliases) 585238423Sjhb # Grr, newaliases only works for an empty DESTDIR. 586238423Sjhb if [ -z "$DESTDIR" ]; then 587238423Sjhb log "newaliases" 588238423Sjhb if [ -z "$dryrun" ]; then 589238423Sjhb newaliases >&3 2>&1 590238423Sjhb fi 591238423Sjhb else 592238423Sjhb NEWALIAS_WARN=yes 593238423Sjhb fi 594238423Sjhb ;; 595238423Sjhb /etc/login.conf) 596238423Sjhb log "cap_mkdb ${DESTDIR}$1" 597238423Sjhb if [ -z "$dryrun" ]; then 598238423Sjhb cap_mkdb ${DESTDIR}$1 >&3 2>&1 599238423Sjhb fi 600238423Sjhb ;; 601238423Sjhb /etc/master.passwd) 602238423Sjhb log "pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1" 603238423Sjhb if [ -z "$dryrun" ]; then 604238423Sjhb pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1 \ 605238423Sjhb >&3 2>&1 606238423Sjhb fi 607238423Sjhb ;; 608238423Sjhb /etc/motd) 609238423Sjhb # /etc/rc.d/motd hardcodes the /etc/motd path. 610238423Sjhb # Don't warn about non-empty DESTDIR's since this 611238423Sjhb # change is only cosmetic anyway. 612238423Sjhb if [ -z "$DESTDIR" ]; then 613238423Sjhb log "sh /etc/rc.d/motd start" 614238423Sjhb if [ -z "$dryrun" ]; then 615238423Sjhb sh /etc/rc.d/motd start >&3 2>&1 616238423Sjhb fi 617238423Sjhb fi 618238423Sjhb ;; 619259960Sjhb /etc/services) 620259960Sjhb log "services_mkdb -q -o $DESTDIR/var/db/services.db" \ 621259960Sjhb "${DESTDIR}$1" 622259960Sjhb if [ -z "$dryrun" ]; then 623259960Sjhb services_mkdb -q -o $DESTDIR/var/db/services.db \ 624259960Sjhb ${DESTDIR}$1 >&3 2>&1 625259960Sjhb fi 626259960Sjhb ;; 627238423Sjhb esac 628238423Sjhb} 629238423Sjhb 630238423Sjhb# Install the "new" version of a file. Returns true if it succeeds 631238423Sjhb# and false otherwise. 632238423Sjhb# 633238423Sjhb# $1 - pathname of the file to install (relative to DESTDIR) 634238423Sjhbinstall_new() 635238423Sjhb{ 636238423Sjhb 637238423Sjhb if ! install_dirs $NEWTREE "$DESTDIR" $1; then 638238423Sjhb return 1 639238423Sjhb fi 640238423Sjhb log "cp -Rp ${NEWTREE}$1 ${DESTDIR}$1" 641238423Sjhb if [ -z "$dryrun" ]; then 642238423Sjhb cp -Rp ${NEWTREE}$1 ${DESTDIR}$1 >&3 2>&1 643238423Sjhb fi 644238423Sjhb post_install_file $1 645238423Sjhb return 0 646238423Sjhb} 647238423Sjhb 648238423Sjhb# Install the "resolved" version of a file. Returns true if it succeeds 649238423Sjhb# and false otherwise. 650238423Sjhb# 651238423Sjhb# $1 - pathname of the file to install (relative to DESTDIR) 652238423Sjhbinstall_resolved() 653238423Sjhb{ 654238423Sjhb 655238423Sjhb # This should always be present since the file is already 656238423Sjhb # there (it caused a conflict). However, it doesn't hurt to 657238423Sjhb # just be safe. 658238423Sjhb if ! install_dirs $NEWTREE "$DESTDIR" $1; then 659238423Sjhb return 1 660238423Sjhb fi 661238423Sjhb 662238423Sjhb log "cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1" 663238423Sjhb cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1 >&3 2>&1 664238423Sjhb post_install_file $1 665238423Sjhb return 0 666238423Sjhb} 667238423Sjhb 668238423Sjhb# Generate a conflict file when a "new" file conflicts with an 669238423Sjhb# existing file in DESTDIR. 670238423Sjhb# 671238423Sjhb# $1 - pathname of the file that conflicts (relative to DESTDIR) 672238423Sjhbnew_conflict() 673238423Sjhb{ 674238423Sjhb 675238423Sjhb if [ -n "$dryrun" ]; then 676238423Sjhb return 677238423Sjhb fi 678238423Sjhb 679238423Sjhb install_dirs $NEWTREE $CONFLICTS $1 680238423Sjhb diff --changed-group-format='<<<<<<< (local) 681238423Sjhb%<======= 682238423Sjhb%>>>>>>>> (stock) 683238423Sjhb' $DESTDIR/$1 $NEWTREE/$1 > $CONFLICTS/$1 684238423Sjhb} 685238423Sjhb 686238423Sjhb# Remove the "old" version of a file. 687238423Sjhb# 688238423Sjhb# $1 - pathname of the old file to remove (relative to DESTDIR) 689238423Sjhbremove_old() 690238423Sjhb{ 691238423Sjhb log "rm -f ${DESTDIR}$1" 692238423Sjhb if [ -z "$dryrun" ]; then 693238423Sjhb rm -f ${DESTDIR}$1 >&3 2>&1 694238423Sjhb fi 695238423Sjhb echo " D $1" 696238423Sjhb} 697238423Sjhb 698238423Sjhb# Update a file that has no local modifications. 699238423Sjhb# 700238423Sjhb# $1 - pathname of the file to update (relative to DESTDIR) 701238423Sjhbupdate_unmodified() 702238423Sjhb{ 703238423Sjhb local new old 704238423Sjhb 705238423Sjhb # If the old file is a directory, then remove it with rmdir 706238423Sjhb # (this should only happen if the file has changed its type 707238423Sjhb # from a directory to a non-directory). If the directory 708238423Sjhb # isn't empty, then fail. This will be reported as a warning 709238423Sjhb # later. 710238423Sjhb if [ -d $DESTDIR/$1 ]; then 711238423Sjhb if empty_destdir $1; then 712238423Sjhb log "rmdir ${DESTDIR}$1" 713238423Sjhb if [ -z "$dryrun" ]; then 714238423Sjhb rmdir ${DESTDIR}$1 >&3 2>&1 715238423Sjhb fi 716238423Sjhb else 717238423Sjhb return 1 718238423Sjhb fi 719238423Sjhb 720238423Sjhb # If both the old and new files are regular files, leave the 721238423Sjhb # existing file. This avoids breaking hard links for /.cshrc 722238423Sjhb # and /.profile. Otherwise, explicitly remove the old file. 723238423Sjhb elif ! [ -f ${DESTDIR}$1 -a -f ${NEWTREE}$1 ]; then 724238423Sjhb log "rm -f ${DESTDIR}$1" 725238423Sjhb if [ -z "$dryrun" ]; then 726238423Sjhb rm -f ${DESTDIR}$1 >&3 2>&1 727238423Sjhb fi 728238423Sjhb fi 729238423Sjhb 730238423Sjhb # If the new file is a directory, note that the old file has 731238423Sjhb # been removed, but don't do anything else for now. The 732238423Sjhb # directory will be installed if needed when new files within 733238423Sjhb # that directory are installed. 734238423Sjhb if [ -d $NEWTREE/$1 ]; then 735238423Sjhb if empty_dir $NEWTREE/$1; then 736238423Sjhb echo " D $file" 737238423Sjhb else 738238423Sjhb echo " U $file" 739238423Sjhb fi 740238423Sjhb elif install_new $1; then 741238423Sjhb echo " U $file" 742238423Sjhb fi 743238423Sjhb return 0 744238423Sjhb} 745238423Sjhb 746238423Sjhb# Update the FreeBSD ID string in a locally modified file to match the 747238423Sjhb# FreeBSD ID string from the "new" version of the file. 748238423Sjhb# 749238423Sjhb# $1 - pathname of the file to update (relative to DESTDIR) 750238423Sjhbupdate_freebsdid() 751238423Sjhb{ 752238423Sjhb local new dest file 753238423Sjhb 754238423Sjhb # If the FreeBSD ID string is removed from the local file, 755238423Sjhb # there is nothing to do. In this case, treat the file as 756238423Sjhb # updated. Otherwise, if either file has more than one 757238423Sjhb # FreeBSD ID string, just punt and let the user handle the 758238423Sjhb # conflict manually. 759238423Sjhb new=`grep -c '\$FreeBSD.*\$' ${NEWTREE}$1` 760238423Sjhb dest=`grep -c '\$FreeBSD.*\$' ${DESTDIR}$1` 761238423Sjhb if [ "$dest" -eq 0 ]; then 762238423Sjhb return 0 763238423Sjhb fi 764238423Sjhb if [ "$dest" -ne 1 -o "$dest" -ne 1 ]; then 765238423Sjhb return 1 766238423Sjhb fi 767238423Sjhb 768238423Sjhb # If the FreeBSD ID string in the new file matches the FreeBSD ID 769238423Sjhb # string in the local file, there is nothing to do. 770238423Sjhb new=`grep '\$FreeBSD.*\$' ${NEWTREE}$1` 771238423Sjhb dest=`grep '\$FreeBSD.*\$' ${DESTDIR}$1` 772238423Sjhb if [ "$new" = "$dest" ]; then 773238423Sjhb return 0 774238423Sjhb fi 775238423Sjhb 776238423Sjhb # Build the new file in three passes. First, copy all the 777238423Sjhb # lines preceding the FreeBSD ID string from the local version 778238423Sjhb # of the file. Second, append the FreeBSD ID string line from 779238423Sjhb # the new version. Finally, append all the lines after the 780238423Sjhb # FreeBSD ID string from the local version of the file. 781238423Sjhb file=`mktemp $WORKDIR/etcupdate-XXXXXXX` 782238423Sjhb awk '/\$FreeBSD.*\$/ { exit } { print }' ${DESTDIR}$1 >> $file 783238423Sjhb awk '/\$FreeBSD.*\$/ { print }' ${NEWTREE}$1 >> $file 784238423Sjhb awk '/\$FreeBSD.*\$/ { ok = 1; next } { if (ok) print }' \ 785238423Sjhb ${DESTDIR}$1 >> $file 786238423Sjhb 787238423Sjhb # As an extra sanity check, fail the attempt if the updated 788238423Sjhb # version of the file has any differences aside from the 789238423Sjhb # FreeBSD ID string. 790238423Sjhb if ! fbsdid_only ${DESTDIR}$1 $file; then 791238423Sjhb rm -f $file 792238423Sjhb return 1 793238423Sjhb fi 794238423Sjhb 795238423Sjhb log "cp $file ${DESTDIR}$1" 796238423Sjhb if [ -z "$dryrun" ]; then 797238423Sjhb cp $file ${DESTDIR}$1 >&3 2>&1 798238423Sjhb fi 799238423Sjhb rm -f $file 800238423Sjhb post_install_file $1 801238423Sjhb echo " M $1" 802238423Sjhb return 0 803238423Sjhb} 804238423Sjhb 805238423Sjhb# Attempt to update a file that has local modifications. This routine 806238423Sjhb# only handles regular files. If the 3-way merge succeeds without 807238423Sjhb# conflicts, the updated file is installed. If the merge fails, the 808238423Sjhb# merged version with conflict markers is left in the CONFLICTS tree. 809238423Sjhb# 810238423Sjhb# $1 - pathname of the file to merge (relative to DESTDIR) 811238423Sjhbmerge_file() 812238423Sjhb{ 813238423Sjhb local res 814238423Sjhb 815238423Sjhb # Try the merge to see if there is a conflict. 816238423Sjhb merge -q -p ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 >/dev/null 2>&3 817238423Sjhb res=$? 818238423Sjhb case $res in 819238423Sjhb 0) 820238423Sjhb # No conflicts, so just redo the merge to the 821238423Sjhb # real file. 822238423Sjhb log "merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1" 823238423Sjhb if [ -z "$dryrun" ]; then 824238423Sjhb merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 825238423Sjhb fi 826238423Sjhb post_install_file $1 827238423Sjhb echo " M $1" 828238423Sjhb ;; 829238423Sjhb 1) 830238423Sjhb # Conflicts, save a version with conflict markers in 831238423Sjhb # the conflicts directory. 832238423Sjhb if [ -z "$dryrun" ]; then 833238423Sjhb install_dirs $NEWTREE $CONFLICTS $1 834238423Sjhb log "cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1" 835238423Sjhb cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1 >&3 2>&1 836238423Sjhb merge -A -q -L "yours" -L "original" -L "new" \ 837238423Sjhb ${CONFLICTS}$1 ${OLDTREE}$1 ${NEWTREE}$1 838238423Sjhb fi 839238423Sjhb echo " C $1" 840238423Sjhb ;; 841238423Sjhb *) 842238423Sjhb panic "merge failed with status $res" 843238423Sjhb ;; 844238423Sjhb esac 845238423Sjhb} 846238423Sjhb 847238423Sjhb# Returns true if a file contains conflict markers from a merge conflict. 848238423Sjhb# 849238423Sjhb# $1 - pathname of the file to resolve (relative to DESTDIR) 850238423Sjhbhas_conflicts() 851238423Sjhb{ 852238423Sjhb 853238423Sjhb egrep -q '^(<{7}|\|{7}|={7}|>{7}) ' $CONFLICTS/$1 854238423Sjhb} 855238423Sjhb 856238423Sjhb# Attempt to resolve a conflict. The user is prompted to choose an 857238423Sjhb# action for each conflict. If the user edits the file, they are 858238423Sjhb# prompted again for an action. The process is very similar to 859238423Sjhb# resolving conflicts after an update or merge with Perforce or 860238423Sjhb# Subversion. The prompts are modelled on a subset of the available 861238423Sjhb# commands for resolving conflicts with Subversion. 862238423Sjhb# 863238423Sjhb# $1 - pathname of the file to resolve (relative to DESTDIR) 864238423Sjhbresolve_conflict() 865238423Sjhb{ 866238423Sjhb local command junk 867238423Sjhb 868238423Sjhb echo "Resolving conflict in '$1':" 869238423Sjhb edit= 870238423Sjhb while true; do 871238423Sjhb # Only display the resolved command if the file 872238423Sjhb # doesn't contain any conflicts. 873238423Sjhb echo -n "Select: (p) postpone, (df) diff-full, (e) edit," 874238423Sjhb if ! has_conflicts $1; then 875238423Sjhb echo -n " (r) resolved," 876238423Sjhb fi 877238423Sjhb echo 878238423Sjhb echo -n " (h) help for more options: " 879238423Sjhb read command 880238423Sjhb case $command in 881238423Sjhb df) 882238423Sjhb diff -u ${DESTDIR}$1 ${CONFLICTS}$1 883238423Sjhb ;; 884238423Sjhb e) 885238423Sjhb $EDITOR ${CONFLICTS}$1 886238423Sjhb ;; 887238423Sjhb h) 888238423Sjhb cat <<EOF 889238423Sjhb (p) postpone - ignore this conflict for now 890238423Sjhb (df) diff-full - show all changes made to merged file 891238423Sjhb (e) edit - change merged file in an editor 892238423Sjhb (r) resolved - accept merged version of file 893238423Sjhb (mf) mine-full - accept local version of entire file (ignore new changes) 894238423Sjhb (tf) theirs-full - accept new version of entire file (lose local changes) 895238423Sjhb (h) help - show this list 896238423SjhbEOF 897238423Sjhb ;; 898238423Sjhb mf) 899238423Sjhb # For mine-full, just delete the 900238423Sjhb # merged file and leave the local 901238423Sjhb # version of the file as-is. 902238423Sjhb rm ${CONFLICTS}$1 903238423Sjhb return 904238423Sjhb ;; 905238423Sjhb p) 906238423Sjhb return 907238423Sjhb ;; 908238423Sjhb r) 909238423Sjhb # If the merged file has conflict 910238423Sjhb # markers, require confirmation. 911238423Sjhb if has_conflicts $1; then 912238423Sjhb echo "File '$1' still has conflicts," \ 913238423Sjhb "are you sure? (y/n) " 914238423Sjhb read junk 915238423Sjhb if [ "$junk" != "y" ]; then 916238423Sjhb continue 917238423Sjhb fi 918238423Sjhb fi 919238423Sjhb 920238423Sjhb if ! install_resolved $1; then 921238423Sjhb panic "Unable to install merged" \ 922238423Sjhb "version of $1" 923238423Sjhb fi 924238423Sjhb rm ${CONFLICTS}$1 925238423Sjhb return 926238423Sjhb ;; 927238423Sjhb tf) 928238423Sjhb # For theirs-full, install the new 929238423Sjhb # version of the file over top of the 930238423Sjhb # existing file. 931238423Sjhb if ! install_new $1; then 932238423Sjhb panic "Unable to install new" \ 933238423Sjhb "version of $1" 934238423Sjhb fi 935238423Sjhb rm ${CONFLICTS}$1 936238423Sjhb return 937238423Sjhb ;; 938238423Sjhb *) 939238423Sjhb echo "Invalid command." 940238423Sjhb ;; 941238423Sjhb esac 942238423Sjhb done 943238423Sjhb} 944238423Sjhb 945238423Sjhb# Handle a file that has been removed from the new tree. If the file 946238423Sjhb# does not exist in DESTDIR, then there is nothing to do. If the file 947238423Sjhb# exists in DESTDIR and is identical to the old version, remove it 948238423Sjhb# from DESTDIR. Otherwise, whine about the conflict but leave the 949238423Sjhb# file in DESTDIR. To handle directories, this uses two passes. The 950238423Sjhb# first pass handles all non-directory files. The second pass handles 951238423Sjhb# just directories and removes them if they are empty. 952238423Sjhb# 953238423Sjhb# If -F is specified, and the only difference in the file in DESTDIR 954238423Sjhb# is a change in the FreeBSD ID string, then remove the file. 955238423Sjhb# 956238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 957238423Sjhbhandle_removed_file() 958238423Sjhb{ 959238423Sjhb local dest file 960238423Sjhb 961238423Sjhb file=$1 962238423Sjhb if ignore $file; then 963238423Sjhb log "IGNORE: removed file $file" 964238423Sjhb return 965238423Sjhb fi 966238423Sjhb 967238423Sjhb compare_fbsdid $DESTDIR/$file $OLDTREE/$file 968238423Sjhb case $? in 969238423Sjhb $COMPARE_EQUAL) 970238423Sjhb if ! [ -d $DESTDIR/$file ]; then 971238423Sjhb remove_old $file 972238423Sjhb fi 973238423Sjhb ;; 974238423Sjhb $COMPARE_ONLYFIRST) 975238423Sjhb panic "Removed file now missing" 976238423Sjhb ;; 977238423Sjhb $COMPARE_ONLYSECOND) 978238423Sjhb # Already removed, nothing to do. 979238423Sjhb ;; 980238423Sjhb $COMPARE_DIFFTYPE|$COMPARE_DIFFLINKS|$COMPARE_DIFFFILES) 981238423Sjhb dest=`file_type $DESTDIR/$file` 982238423Sjhb warn "Modified $dest remains: $file" 983238423Sjhb ;; 984238423Sjhb esac 985238423Sjhb} 986238423Sjhb 987238423Sjhb# Handle a directory that has been removed from the new tree. Only 988238423Sjhb# remove the directory if it is empty. 989238423Sjhb# 990238423Sjhb# $1 - pathname of the directory (relative to DESTDIR) 991238423Sjhbhandle_removed_directory() 992238423Sjhb{ 993238423Sjhb local dir 994238423Sjhb 995238423Sjhb dir=$1 996238423Sjhb if ignore $dir; then 997238423Sjhb log "IGNORE: removed dir $dir" 998238423Sjhb return 999238423Sjhb fi 1000238423Sjhb 1001238423Sjhb if [ -d $DESTDIR/$dir -a -d $OLDTREE/$dir ]; then 1002238423Sjhb if empty_destdir $dir; then 1003238423Sjhb log "rmdir ${DESTDIR}$dir" 1004238423Sjhb if [ -z "$dryrun" ]; then 1005238423Sjhb rmdir ${DESTDIR}$dir >/dev/null 2>&1 1006238423Sjhb fi 1007238423Sjhb echo " D $dir" 1008238423Sjhb else 1009238423Sjhb warn "Non-empty directory remains: $dir" 1010238423Sjhb fi 1011238423Sjhb fi 1012238423Sjhb} 1013238423Sjhb 1014238423Sjhb# Handle a file that exists in both the old and new trees. If the 1015238423Sjhb# file has not changed in the old and new trees, there is nothing to 1016238423Sjhb# do. If the file in the destination directory matches the new file, 1017238423Sjhb# there is nothing to do. If the file in the destination directory 1018238423Sjhb# matches the old file, then the new file should be installed. 1019238423Sjhb# Everything else becomes some sort of conflict with more detailed 1020238423Sjhb# handling. 1021238423Sjhb# 1022238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 1023238423Sjhbhandle_modified_file() 1024238423Sjhb{ 1025238423Sjhb local cmp dest file new newdestcmp old 1026238423Sjhb 1027238423Sjhb file=$1 1028238423Sjhb if ignore $file; then 1029238423Sjhb log "IGNORE: modified file $file" 1030238423Sjhb return 1031238423Sjhb fi 1032238423Sjhb 1033238423Sjhb compare $OLDTREE/$file $NEWTREE/$file 1034238423Sjhb cmp=$? 1035238423Sjhb if [ $cmp -eq $COMPARE_EQUAL ]; then 1036238423Sjhb return 1037238423Sjhb fi 1038238423Sjhb 1039238423Sjhb if [ $cmp -eq $COMPARE_ONLYFIRST -o $cmp -eq $COMPARE_ONLYSECOND ]; then 1040238423Sjhb panic "Changed file now missing" 1041238423Sjhb fi 1042238423Sjhb 1043238423Sjhb compare $NEWTREE/$file $DESTDIR/$file 1044238423Sjhb newdestcmp=$? 1045238423Sjhb if [ $newdestcmp -eq $COMPARE_EQUAL ]; then 1046238423Sjhb return 1047238423Sjhb fi 1048238423Sjhb 1049238423Sjhb # If the only change in the new file versus the destination 1050238423Sjhb # file is a change in the FreeBSD ID string and -F is 1051238423Sjhb # specified, just install the new file. 1052238423Sjhb if [ -n "$FREEBSD_ID" -a $newdestcmp -eq $COMPARE_DIFFFILES ] && \ 1053238423Sjhb fbsdid_only $NEWTREE/$file $DESTDIR/$file; then 1054238423Sjhb if update_unmodified $file; then 1055238423Sjhb return 1056238423Sjhb else 1057238423Sjhb panic "Updating FreeBSD ID string failed" 1058238423Sjhb fi 1059238423Sjhb fi 1060238423Sjhb 1061238423Sjhb # If the local file is the same as the old file, install the 1062238423Sjhb # new file. If -F is specified and the only local change is 1063238423Sjhb # in the FreeBSD ID string, then install the new file as well. 1064238423Sjhb if compare_fbsdid $OLDTREE/$file $DESTDIR/$file; then 1065238423Sjhb if update_unmodified $file; then 1066238423Sjhb return 1067238423Sjhb fi 1068238423Sjhb fi 1069238423Sjhb 1070238423Sjhb # If the file was removed from the dest tree, just whine. 1071238423Sjhb if [ $newdestcmp -eq $COMPARE_ONLYFIRST ]; then 1072238423Sjhb # If the removed file matches an ALWAYS_INSTALL glob, 1073238423Sjhb # then just install the new version of the file. 1074238423Sjhb if always_install $file; then 1075238423Sjhb log "ALWAYS: adding $file" 1076238423Sjhb if ! [ -d $NEWTREE/$file ]; then 1077238423Sjhb if install_new $file; then 1078238423Sjhb echo " A $file" 1079238423Sjhb fi 1080238423Sjhb fi 1081238423Sjhb return 1082238423Sjhb fi 1083238423Sjhb 1084259960Sjhb # If the only change in the new file versus the old 1085259960Sjhb # file is a change in the FreeBSD ID string and -F is 1086259960Sjhb # specified, don't warn. 1087259960Sjhb if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ 1088259960Sjhb fbsdid_only $OLDTREE/$file $NEWTREE/$file; then 1089259960Sjhb return 1090259960Sjhb fi 1091259960Sjhb 1092238423Sjhb case $cmp in 1093238423Sjhb $COMPARE_DIFFTYPE) 1094238423Sjhb old=`file_type $OLDTREE/$file` 1095238423Sjhb new=`file_type $NEWTREE/$file` 1096238423Sjhb warn "Remove mismatch: $file ($old became $new)" 1097238423Sjhb ;; 1098238423Sjhb $COMPARE_DIFFLINKS) 1099238423Sjhb old=`readlink $OLDTREE/$file` 1100238423Sjhb new=`readlink $NEWTREE/$file` 1101238423Sjhb warn \ 1102238423Sjhb "Removed link changed: $file (\"$old\" became \"$new\")" 1103238423Sjhb ;; 1104238423Sjhb $COMPARE_DIFFFILES) 1105238423Sjhb warn "Removed file changed: $file" 1106238423Sjhb ;; 1107238423Sjhb esac 1108238423Sjhb return 1109238423Sjhb fi 1110238423Sjhb 1111238423Sjhb # Treat the file as unmodified and force install of the new 1112238423Sjhb # file if it matches an ALWAYS_INSTALL glob. If the update 1113238423Sjhb # attempt fails, then fall through to the normal case so a 1114238423Sjhb # warning is generated. 1115238423Sjhb if always_install $file; then 1116238423Sjhb log "ALWAYS: updating $file" 1117238423Sjhb if update_unmodified $file; then 1118238423Sjhb return 1119238423Sjhb fi 1120238423Sjhb fi 1121238423Sjhb 1122259960Sjhb # If the only change in the new file versus the old file is a 1123259960Sjhb # change in the FreeBSD ID string and -F is specified, just 1124259960Sjhb # update the FreeBSD ID string in the local file. 1125259960Sjhb if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ 1126259960Sjhb fbsdid_only $OLDTREE/$file $NEWTREE/$file; then 1127259960Sjhb if update_freebsdid $file; then 1128259960Sjhb continue 1129259960Sjhb fi 1130259960Sjhb fi 1131259960Sjhb 1132238423Sjhb # If the file changed types between the old and new trees but 1133238423Sjhb # the files in the new and dest tree are both of the same 1134238423Sjhb # type, treat it like an added file just comparing the new and 1135238423Sjhb # dest files. 1136238423Sjhb if [ $cmp -eq $COMPARE_DIFFTYPE ]; then 1137238423Sjhb case $newdestcmp in 1138238423Sjhb $COMPARE_DIFFLINKS) 1139238423Sjhb new=`readlink $NEWTREE/$file` 1140238423Sjhb dest=`readlink $DESTDIR/$file` 1141238423Sjhb warn \ 1142238423Sjhb "New link conflict: $file (\"$new\" vs \"$dest\")" 1143238423Sjhb return 1144238423Sjhb ;; 1145238423Sjhb $COMPARE_DIFFFILES) 1146238423Sjhb new_conflict $file 1147238423Sjhb echo " C $file" 1148238423Sjhb return 1149238423Sjhb ;; 1150238423Sjhb esac 1151238423Sjhb else 1152238423Sjhb # If the file has not changed types between the old 1153238423Sjhb # and new trees, but it is a different type in 1154238423Sjhb # DESTDIR, then just warn. 1155238423Sjhb if [ $newdestcmp -eq $COMPARE_DIFFTYPE ]; then 1156238423Sjhb new=`file_type $NEWTREE/$file` 1157238423Sjhb dest=`file_type $DESTDIR/$file` 1158238423Sjhb warn "Modified mismatch: $file ($new vs $dest)" 1159238423Sjhb return 1160238423Sjhb fi 1161238423Sjhb fi 1162238423Sjhb 1163238423Sjhb case $cmp in 1164238423Sjhb $COMPARE_DIFFTYPE) 1165238423Sjhb old=`file_type $OLDTREE/$file` 1166238423Sjhb new=`file_type $NEWTREE/$file` 1167238423Sjhb dest=`file_type $DESTDIR/$file` 1168238423Sjhb warn "Modified $dest changed: $file ($old became $new)" 1169238423Sjhb ;; 1170238423Sjhb $COMPARE_DIFFLINKS) 1171238423Sjhb old=`readlink $OLDTREE/$file` 1172238423Sjhb new=`readlink $NEWTREE/$file` 1173238423Sjhb warn \ 1174238423Sjhb "Modified link changed: $file (\"$old\" became \"$new\")" 1175238423Sjhb ;; 1176238423Sjhb $COMPARE_DIFFFILES) 1177238423Sjhb merge_file $file 1178238423Sjhb ;; 1179238423Sjhb esac 1180238423Sjhb} 1181238423Sjhb 1182238423Sjhb# Handle a file that has been added in the new tree. If the file does 1183238423Sjhb# not exist in DESTDIR, simply copy the file into DESTDIR. If the 1184238423Sjhb# file exists in the DESTDIR and is identical to the new version, do 1185238423Sjhb# nothing. Otherwise, generate a diff of the two versions of the file 1186238423Sjhb# and mark it as a conflict. 1187238423Sjhb# 1188238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 1189238423Sjhbhandle_added_file() 1190238423Sjhb{ 1191238423Sjhb local cmp dest file new 1192238423Sjhb 1193238423Sjhb file=$1 1194238423Sjhb if ignore $file; then 1195238423Sjhb log "IGNORE: added file $file" 1196238423Sjhb return 1197238423Sjhb fi 1198238423Sjhb 1199238423Sjhb compare $DESTDIR/$file $NEWTREE/$file 1200238423Sjhb cmp=$? 1201238423Sjhb case $cmp in 1202238423Sjhb $COMPARE_EQUAL) 1203238423Sjhb return 1204238423Sjhb ;; 1205238423Sjhb $COMPARE_ONLYFIRST) 1206238423Sjhb panic "Added file now missing" 1207238423Sjhb ;; 1208238423Sjhb $COMPARE_ONLYSECOND) 1209238423Sjhb # Ignore new directories. They will be 1210238423Sjhb # created as needed when non-directory nodes 1211238423Sjhb # are installed. 1212238423Sjhb if ! [ -d $NEWTREE/$file ]; then 1213238423Sjhb if install_new $file; then 1214238423Sjhb echo " A $file" 1215238423Sjhb fi 1216238423Sjhb fi 1217238423Sjhb return 1218238423Sjhb ;; 1219238423Sjhb esac 1220238423Sjhb 1221238423Sjhb 1222238423Sjhb # Treat the file as unmodified and force install of the new 1223238423Sjhb # file if it matches an ALWAYS_INSTALL glob. If the update 1224238423Sjhb # attempt fails, then fall through to the normal case so a 1225238423Sjhb # warning is generated. 1226238423Sjhb if always_install $file; then 1227238423Sjhb log "ALWAYS: updating $file" 1228238423Sjhb if update_unmodified $file; then 1229238423Sjhb return 1230238423Sjhb fi 1231238423Sjhb fi 1232238423Sjhb 1233238423Sjhb case $cmp in 1234238423Sjhb $COMPARE_DIFFTYPE) 1235238423Sjhb new=`file_type $NEWTREE/$file` 1236238423Sjhb dest=`file_type $DESTDIR/$file` 1237238423Sjhb warn "New file mismatch: $file ($new vs $dest)" 1238238423Sjhb ;; 1239238423Sjhb $COMPARE_DIFFLINKS) 1240238423Sjhb new=`readlink $NEWTREE/$file` 1241238423Sjhb dest=`readlink $DESTDIR/$file` 1242238423Sjhb warn "New link conflict: $file (\"$new\" vs \"$dest\")" 1243238423Sjhb ;; 1244238423Sjhb $COMPARE_DIFFFILES) 1245238423Sjhb # If the only change in the new file versus 1246238423Sjhb # the destination file is a change in the 1247238423Sjhb # FreeBSD ID string and -F is specified, just 1248238423Sjhb # install the new file. 1249238423Sjhb if [ -n "$FREEBSD_ID" ] && \ 1250238423Sjhb fbsdid_only $NEWTREE/$file $DESTDIR/$file; then 1251238423Sjhb if update_unmodified $file; then 1252238423Sjhb return 1253238423Sjhb else 1254238423Sjhb panic \ 1255238423Sjhb "Updating FreeBSD ID string failed" 1256238423Sjhb fi 1257238423Sjhb fi 1258238423Sjhb 1259238423Sjhb new_conflict $file 1260238423Sjhb echo " C $file" 1261238423Sjhb ;; 1262238423Sjhb esac 1263238423Sjhb} 1264238423Sjhb 1265238423Sjhb# Main routines for each command 1266238423Sjhb 1267238423Sjhb# Build a new tree and save it in a tarball. 1268238423Sjhbbuild_cmd() 1269238423Sjhb{ 1270238423Sjhb local dir 1271238423Sjhb 1272238423Sjhb if [ $# -ne 1 ]; then 1273238423Sjhb echo "Missing required tarball." 1274238423Sjhb echo 1275238423Sjhb usage 1276238423Sjhb fi 1277238423Sjhb 1278238423Sjhb log "build command: $1" 1279238423Sjhb 1280238423Sjhb # Create a temporary directory to hold the tree 1281238423Sjhb dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` 1282238423Sjhb if [ $? -ne 0 ]; then 1283238423Sjhb echo "Unable to create temporary directory." 1284238423Sjhb exit 1 1285238423Sjhb fi 1286238423Sjhb if ! build_tree $dir; then 1287238423Sjhb echo "Failed to build tree." 1288238423Sjhb remove_tree $dir 1289238423Sjhb exit 1 1290238423Sjhb fi 1291238423Sjhb if ! tar cfj $1 -C $dir . >&3 2>&1; then 1292238423Sjhb echo "Failed to create tarball." 1293238423Sjhb remove_tree $dir 1294238423Sjhb exit 1 1295238423Sjhb fi 1296238423Sjhb remove_tree $dir 1297238423Sjhb} 1298238423Sjhb 1299238423Sjhb# Output a diff comparing the tree at DESTDIR to the current 1300238423Sjhb# unmodified tree. Note that this diff does not include files that 1301238423Sjhb# are present in DESTDIR but not in the unmodified tree. 1302238423Sjhbdiff_cmd() 1303238423Sjhb{ 1304238423Sjhb local file 1305238423Sjhb 1306238423Sjhb if [ $# -ne 0 ]; then 1307238423Sjhb usage 1308238423Sjhb fi 1309238423Sjhb 1310238423Sjhb # Requires an unmodified tree to diff against. 1311238423Sjhb if ! [ -d $NEWTREE ]; then 1312238423Sjhb echo "Reference tree to diff against unavailable." 1313238423Sjhb exit 1 1314238423Sjhb fi 1315238423Sjhb 1316238423Sjhb # Unfortunately, diff alone does not quite provide the right 1317238423Sjhb # level of options that we want, so improvise. 1318238423Sjhb for file in `(cd $NEWTREE; find .) | sed -e 's/^\.//'`; do 1319238423Sjhb if ignore $file; then 1320238423Sjhb continue 1321238423Sjhb fi 1322238423Sjhb 1323238423Sjhb diffnode $NEWTREE "$DESTDIR" $file "stock" "local" 1324238423Sjhb done 1325238423Sjhb} 1326238423Sjhb 1327238423Sjhb# Just extract a new tree into NEWTREE either by building a tree or 1328238423Sjhb# extracting a tarball. This can be used to bootstrap updates by 1329238423Sjhb# initializing the current "stock" tree to match the currently 1330238423Sjhb# installed system. 1331238423Sjhb# 1332238423Sjhb# Unlike 'update', this command does not rotate or preserve an 1333238423Sjhb# existing NEWTREE, it just replaces any existing tree. 1334238423Sjhbextract_cmd() 1335238423Sjhb{ 1336238423Sjhb 1337238423Sjhb if [ $# -ne 0 ]; then 1338238423Sjhb usage 1339238423Sjhb fi 1340238423Sjhb 1341238423Sjhb log "extract command: tarball=$tarball" 1342238423Sjhb 1343238423Sjhb if [ -d $NEWTREE ]; then 1344238423Sjhb if ! remove_tree $NEWTREE; then 1345238423Sjhb echo "Unable to remove current tree." 1346238423Sjhb exit 1 1347238423Sjhb fi 1348238423Sjhb fi 1349238423Sjhb 1350238423Sjhb extract_tree 1351238423Sjhb} 1352238423Sjhb 1353238423Sjhb# Resolve conflicts left from an earlier merge. 1354238423Sjhbresolve_cmd() 1355238423Sjhb{ 1356238423Sjhb local conflicts 1357238423Sjhb 1358238423Sjhb if [ $# -ne 0 ]; then 1359238423Sjhb usage 1360238423Sjhb fi 1361238423Sjhb 1362238423Sjhb if ! [ -d $CONFLICTS ]; then 1363238423Sjhb return 1364238423Sjhb fi 1365238423Sjhb 1366259960Sjhb if ! [ -d $NEWTREE ]; then 1367259960Sjhb echo "The current tree is not present to resolve conflicts." 1368259960Sjhb exit 1 1369259960Sjhb fi 1370259960Sjhb 1371238423Sjhb conflicts=`(cd $CONFLICTS; find . ! -type d) | sed -e 's/^\.//'` 1372238423Sjhb for file in $conflicts; do 1373238423Sjhb resolve_conflict $file 1374238423Sjhb done 1375238423Sjhb 1376238423Sjhb if [ -n "$NEWALIAS_WARN" ]; then 1377238423Sjhb warn "Needs update: /etc/mail/aliases.db" \ 1378238423Sjhb "(requires manual update via newaliases(1))" 1379238423Sjhb echo 1380238423Sjhb echo "Warnings:" 1381238423Sjhb echo " Needs update: /etc/mail/aliases.db" \ 1382238423Sjhb "(requires manual update via newaliases(1))" 1383238423Sjhb fi 1384238423Sjhb} 1385238423Sjhb 1386238423Sjhb# Report a summary of the previous merge. Specifically, list any 1387238423Sjhb# remaining conflicts followed by any warnings from the previous 1388238423Sjhb# update. 1389238423Sjhbstatus_cmd() 1390238423Sjhb{ 1391238423Sjhb 1392238423Sjhb if [ $# -ne 0 ]; then 1393238423Sjhb usage 1394238423Sjhb fi 1395238423Sjhb 1396238423Sjhb if [ -d $CONFLICTS ]; then 1397238423Sjhb (cd $CONFLICTS; find . ! -type d) | sed -e 's/^\./ C /' 1398238423Sjhb fi 1399238423Sjhb if [ -s $WARNINGS ]; then 1400238423Sjhb echo "Warnings:" 1401238423Sjhb cat $WARNINGS 1402238423Sjhb fi 1403238423Sjhb} 1404238423Sjhb 1405238423Sjhb# Perform an actual merge. The new tree can either already exist (if 1406238423Sjhb# rerunning a merge), be extracted from a tarball, or generated from a 1407238423Sjhb# source tree. 1408238423Sjhbupdate_cmd() 1409238423Sjhb{ 1410238423Sjhb local dir 1411238423Sjhb 1412238423Sjhb if [ $# -ne 0 ]; then 1413238423Sjhb usage 1414238423Sjhb fi 1415238423Sjhb 1416259960Sjhb log "update command: rerun=$rerun tarball=$tarball preworld=$preworld" 1417238423Sjhb 1418238423Sjhb if [ `id -u` -ne 0 ]; then 1419238423Sjhb echo "Must be root to update a tree." 1420238423Sjhb exit 1 1421238423Sjhb fi 1422238423Sjhb 1423238423Sjhb # Enforce a sane umask 1424238423Sjhb umask 022 1425238423Sjhb 1426238423Sjhb # XXX: Should existing conflicts be ignored and removed during 1427238423Sjhb # a rerun? 1428238423Sjhb 1429238423Sjhb # Trim the conflicts tree. Whine if there is anything left. 1430238423Sjhb if [ -e $CONFLICTS ]; then 1431238423Sjhb find -d $CONFLICTS -type d -empty -delete >&3 2>&1 1432238423Sjhb rmdir $CONFLICTS >&3 2>&1 1433238423Sjhb fi 1434238423Sjhb if [ -d $CONFLICTS ]; then 1435238423Sjhb echo "Conflicts remain from previous update, aborting." 1436238423Sjhb exit 1 1437238423Sjhb fi 1438238423Sjhb 1439238423Sjhb if [ -z "$rerun" ]; then 1440238423Sjhb # For a dryrun that is not a rerun, do not rotate the existing 1441238423Sjhb # stock tree. Instead, extract a tree to a temporary directory 1442238423Sjhb # and use that for the comparison. 1443238423Sjhb if [ -n "$dryrun" ]; then 1444238423Sjhb dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` 1445238423Sjhb if [ $? -ne 0 ]; then 1446238423Sjhb echo "Unable to create temporary directory." 1447238423Sjhb exit 1 1448238423Sjhb fi 1449259960Sjhb 1450259960Sjhb # A pre-world dryrun has already set OLDTREE to 1451259960Sjhb # point to the current stock tree. 1452259960Sjhb if [ -z "$preworld" ]; then 1453259960Sjhb OLDTREE=$NEWTREE 1454259960Sjhb fi 1455238423Sjhb NEWTREE=$dir 1456238423Sjhb 1457259960Sjhb # For a pre-world update, blow away any pre-existing 1458259960Sjhb # NEWTREE. 1459259960Sjhb elif [ -n "$preworld" ]; then 1460259960Sjhb if ! remove_tree $NEWTREE; then 1461259960Sjhb echo "Unable to remove pre-world tree." 1462259960Sjhb exit 1 1463259960Sjhb fi 1464259960Sjhb 1465238423Sjhb # Rotate the existing stock tree to the old tree. 1466238423Sjhb elif [ -d $NEWTREE ]; then 1467238423Sjhb # First, delete the previous old tree if it exists. 1468238423Sjhb if ! remove_tree $OLDTREE; then 1469238423Sjhb echo "Unable to remove old tree." 1470238423Sjhb exit 1 1471238423Sjhb fi 1472238423Sjhb 1473238423Sjhb # Move the current stock tree. 1474238423Sjhb if ! mv $NEWTREE $OLDTREE >&3 2>&1; then 1475238423Sjhb echo "Unable to rename current stock tree." 1476238423Sjhb exit 1 1477238423Sjhb fi 1478238423Sjhb fi 1479238423Sjhb 1480238423Sjhb if ! [ -d $OLDTREE ]; then 1481238423Sjhb cat <<EOF 1482238423SjhbNo previous tree to compare against, a sane comparison is not possible. 1483238423SjhbEOF 1484238423Sjhb log "No previous tree to compare against." 1485238423Sjhb if [ -n "$dir" ]; then 1486238423Sjhb rmdir $dir 1487238423Sjhb fi 1488238423Sjhb exit 1 1489238423Sjhb fi 1490238423Sjhb 1491238423Sjhb # Populate the new tree. 1492238423Sjhb extract_tree 1493238423Sjhb fi 1494238423Sjhb 1495238423Sjhb # Build lists of nodes in the old and new trees. 1496238423Sjhb (cd $OLDTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/old.files 1497238423Sjhb (cd $NEWTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/new.files 1498238423Sjhb 1499238423Sjhb # Split the files up into three groups using comm. 1500238423Sjhb comm -23 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/removed.files 1501238423Sjhb comm -13 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/added.files 1502238423Sjhb comm -12 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/both.files 1503238423Sjhb 1504238423Sjhb # Initialize conflicts and warnings handling. 1505238423Sjhb rm -f $WARNINGS 1506238423Sjhb mkdir -p $CONFLICTS 1507259960Sjhb 1508259960Sjhb # Ignore removed files for the pre-world case. A pre-world 1509259960Sjhb # update uses a stripped-down tree. 1510259960Sjhb if [ -n "$preworld" ]; then 1511259960Sjhb > $WORKDIR/removed.files 1512259960Sjhb fi 1513238423Sjhb 1514238423Sjhb # The order for the following sections is important. In the 1515238423Sjhb # odd case that a directory is converted into a file, the 1516238423Sjhb # existing subfiles need to be removed if possible before the 1517238423Sjhb # file is converted. Similarly, in the case that a file is 1518238423Sjhb # converted into a directory, the file needs to be converted 1519238423Sjhb # into a directory if possible before the new files are added. 1520238423Sjhb 1521238423Sjhb # First, handle removed files. 1522238423Sjhb for file in `cat $WORKDIR/removed.files`; do 1523238423Sjhb handle_removed_file $file 1524238423Sjhb done 1525238423Sjhb 1526238423Sjhb # For the directory pass, reverse sort the list to effect a 1527238423Sjhb # depth-first traversal. This is needed to ensure that if a 1528238423Sjhb # directory with subdirectories is removed, the entire 1529238423Sjhb # directory is removed if there are no local modifications. 1530238423Sjhb for file in `sort -r $WORKDIR/removed.files`; do 1531238423Sjhb handle_removed_directory $file 1532238423Sjhb done 1533238423Sjhb 1534238423Sjhb # Second, handle files that exist in both the old and new 1535238423Sjhb # trees. 1536238423Sjhb for file in `cat $WORKDIR/both.files`; do 1537238423Sjhb handle_modified_file $file 1538238423Sjhb done 1539238423Sjhb 1540238423Sjhb # Finally, handle newly added files. 1541238423Sjhb for file in `cat $WORKDIR/added.files`; do 1542238423Sjhb handle_added_file $file 1543238423Sjhb done 1544238423Sjhb 1545238423Sjhb if [ -n "$NEWALIAS_WARN" ]; then 1546238423Sjhb warn "Needs update: /etc/mail/aliases.db" \ 1547238423Sjhb "(requires manual update via newaliases(1))" 1548238423Sjhb fi 1549238423Sjhb 1550259960Sjhb # Run any special one-off commands after an update has completed. 1551259960Sjhb post_update 1552259960Sjhb 1553238423Sjhb if [ -s $WARNINGS ]; then 1554238423Sjhb echo "Warnings:" 1555238423Sjhb cat $WARNINGS 1556238423Sjhb fi 1557238423Sjhb 1558238423Sjhb if [ -n "$dir" ]; then 1559238423Sjhb if [ -z "$dryrun" -o -n "$rerun" ]; then 1560238423Sjhb panic "Should not have a temporary directory" 1561238423Sjhb fi 1562238423Sjhb 1563238423Sjhb remove_tree $dir 1564238423Sjhb fi 1565238423Sjhb} 1566238423Sjhb 1567238423Sjhb# Determine which command we are executing. A command may be 1568238423Sjhb# specified as the first word. If one is not specified then 'update' 1569238423Sjhb# is assumed as the default command. 1570238423Sjhbcommand="update" 1571238423Sjhbif [ $# -gt 0 ]; then 1572238423Sjhb case "$1" in 1573238423Sjhb build|diff|extract|status|resolve) 1574238423Sjhb command="$1" 1575238423Sjhb shift 1576238423Sjhb ;; 1577238423Sjhb -*) 1578238423Sjhb # If first arg is an option, assume the 1579238423Sjhb # default command. 1580238423Sjhb ;; 1581238423Sjhb *) 1582238423Sjhb usage 1583238423Sjhb ;; 1584238423Sjhb esac 1585238423Sjhbfi 1586238423Sjhb 1587238423Sjhb# Set default variable values. 1588238423Sjhb 1589238423Sjhb# The path to the source tree used to build trees. 1590238423SjhbSRCDIR=/usr/src 1591238423Sjhb 1592238423Sjhb# The destination directory where the modified files live. 1593238423SjhbDESTDIR= 1594238423Sjhb 1595238423Sjhb# Ignore changes in the FreeBSD ID string. 1596238423SjhbFREEBSD_ID= 1597238423Sjhb 1598238423Sjhb# Files that should always have the new version of the file installed. 1599238423SjhbALWAYS_INSTALL= 1600238423Sjhb 1601238423Sjhb# Files to ignore and never update during a merge. 1602238423SjhbIGNORE_FILES= 1603238423Sjhb 1604238423Sjhb# Flags to pass to 'make' when building a tree. 1605238423SjhbMAKE_OPTIONS= 1606238423Sjhb 1607238423Sjhb# Include a config file if it exists. Note that command line options 1608238423Sjhb# override any settings in the config file. More details are in the 1609238423Sjhb# manual, but in general the following variables can be set: 1610238423Sjhb# - ALWAYS_INSTALL 1611238423Sjhb# - DESTDIR 1612238423Sjhb# - EDITOR 1613238423Sjhb# - FREEBSD_ID 1614238423Sjhb# - IGNORE_FILES 1615238423Sjhb# - LOGFILE 1616238423Sjhb# - MAKE_OPTIONS 1617238423Sjhb# - SRCDIR 1618238423Sjhb# - WORKDIR 1619238423Sjhbif [ -r /etc/etcupdate.conf ]; then 1620238423Sjhb . /etc/etcupdate.conf 1621238423Sjhbfi 1622238423Sjhb 1623238423Sjhb# Parse command line options 1624238423Sjhbtarball= 1625238423Sjhbrerun= 1626238423Sjhbalways= 1627238423Sjhbdryrun= 1628238423Sjhbignore= 1629238423Sjhbnobuild= 1630259960Sjhbpreworld= 1631259960Sjhbwhile getopts "d:nprs:t:A:BD:FI:L:M:" option; do 1632238423Sjhb case "$option" in 1633238423Sjhb d) 1634238423Sjhb WORKDIR=$OPTARG 1635238423Sjhb ;; 1636238423Sjhb n) 1637238423Sjhb dryrun=YES 1638238423Sjhb ;; 1639259960Sjhb p) 1640259960Sjhb preworld=YES 1641259960Sjhb ;; 1642238423Sjhb r) 1643238423Sjhb rerun=YES 1644238423Sjhb ;; 1645238423Sjhb s) 1646238423Sjhb SRCDIR=$OPTARG 1647238423Sjhb ;; 1648238423Sjhb t) 1649238423Sjhb tarball=$OPTARG 1650238423Sjhb ;; 1651238423Sjhb A) 1652238423Sjhb # To allow this option to be specified 1653238423Sjhb # multiple times, accumulate command-line 1654238423Sjhb # specified patterns in an 'always' variable 1655238423Sjhb # and use that to overwrite ALWAYS_INSTALL 1656238423Sjhb # after parsing all options. Need to be 1657238423Sjhb # careful here with globbing expansion. 1658238423Sjhb set -o noglob 1659238423Sjhb always="$always $OPTARG" 1660238423Sjhb set +o noglob 1661238423Sjhb ;; 1662238423Sjhb B) 1663238423Sjhb nobuild=YES 1664238423Sjhb ;; 1665238423Sjhb D) 1666238423Sjhb DESTDIR=$OPTARG 1667238423Sjhb ;; 1668238423Sjhb F) 1669238423Sjhb FREEBSD_ID=YES 1670238423Sjhb ;; 1671238423Sjhb I) 1672238423Sjhb # To allow this option to be specified 1673238423Sjhb # multiple times, accumulate command-line 1674238423Sjhb # specified patterns in an 'ignore' variable 1675238423Sjhb # and use that to overwrite IGNORE_FILES after 1676238423Sjhb # parsing all options. Need to be careful 1677238423Sjhb # here with globbing expansion. 1678238423Sjhb set -o noglob 1679238423Sjhb ignore="$ignore $OPTARG" 1680238423Sjhb set +o noglob 1681238423Sjhb ;; 1682238423Sjhb L) 1683238423Sjhb LOGFILE=$OPTARG 1684238423Sjhb ;; 1685238423Sjhb M) 1686238423Sjhb MAKE_OPTIONS="$OPTARG" 1687238423Sjhb ;; 1688238423Sjhb *) 1689238423Sjhb echo 1690238423Sjhb usage 1691238423Sjhb ;; 1692238423Sjhb esac 1693238423Sjhbdone 1694238423Sjhbshift $((OPTIND - 1)) 1695238423Sjhb 1696238423Sjhb# Allow -A command line options to override ALWAYS_INSTALL set from 1697238423Sjhb# the config file. 1698238423Sjhbset -o noglob 1699238423Sjhbif [ -n "$always" ]; then 1700238423Sjhb ALWAYS_INSTALL="$always" 1701238423Sjhbfi 1702238423Sjhb 1703238423Sjhb# Allow -I command line options to override IGNORE_FILES set from the 1704238423Sjhb# config file. 1705238423Sjhbif [ -n "$ignore" ]; then 1706238423Sjhb IGNORE_FILES="$ignore" 1707238423Sjhbfi 1708238423Sjhbset +o noglob 1709238423Sjhb 1710238423Sjhb# Where the "old" and "new" trees are stored. 1711238423SjhbWORKDIR=${WORKDIR:-$DESTDIR/var/db/etcupdate} 1712238423Sjhb 1713238423Sjhb# Log file for verbose output from program that are run. The log file 1714238423Sjhb# is opened on fd '3'. 1715238423SjhbLOGFILE=${LOGFILE:-$WORKDIR/log} 1716238423Sjhb 1717238423Sjhb# The path of the "old" tree 1718238423SjhbOLDTREE=$WORKDIR/old 1719238423Sjhb 1720238423Sjhb# The path of the "new" tree 1721238423SjhbNEWTREE=$WORKDIR/current 1722238423Sjhb 1723238423Sjhb# The path of the "conflicts" tree where files with merge conflicts are saved. 1724238423SjhbCONFLICTS=$WORKDIR/conflicts 1725238423Sjhb 1726238423Sjhb# The path of the "warnings" file that accumulates warning notes from an update. 1727238423SjhbWARNINGS=$WORKDIR/warnings 1728238423Sjhb 1729238423Sjhb# Use $EDITOR for resolving conflicts. If it is not set, default to vi. 1730238423SjhbEDITOR=${EDITOR:-/usr/bin/vi} 1731238423Sjhb 1732259960Sjhb# Files that need to be updated before installworld. 1733259960SjhbPREWORLD_FILES="etc/master.passwd etc/group" 1734259960Sjhb 1735238423Sjhb# Handle command-specific argument processing such as complaining 1736238423Sjhb# about unsupported options. Since the configuration file is always 1737238423Sjhb# included, do not complain about extra command line arguments that 1738238423Sjhb# may have been set via the config file rather than the command line. 1739238423Sjhbcase $command in 1740238423Sjhb update) 1741238423Sjhb if [ -n "$rerun" -a -n "$tarball" ]; then 1742238423Sjhb echo "Only one of -r or -t can be specified." 1743238423Sjhb echo 1744238423Sjhb usage 1745238423Sjhb fi 1746259960Sjhb if [ -n "$rerun" -a -n "$preworld" ]; then 1747259960Sjhb echo "Only one of -p or -r can be specified." 1748259960Sjhb echo 1749259960Sjhb usage 1750259960Sjhb fi 1751238423Sjhb ;; 1752259960Sjhb build|diff|status) 1753259960Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" -o \ 1754259960Sjhb -n "$preworld" ]; then 1755259960Sjhb usage 1756259960Sjhb fi 1757259960Sjhb ;; 1758259960Sjhb resolve) 1759238423Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" ]; then 1760238423Sjhb usage 1761238423Sjhb fi 1762238423Sjhb ;; 1763238423Sjhb extract) 1764259960Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$preworld" ]; then 1765238423Sjhb usage 1766238423Sjhb fi 1767238423Sjhb ;; 1768238423Sjhbesac 1769238423Sjhb 1770259960Sjhb# Pre-world mode uses a different set of trees. It leaves the current 1771259960Sjhb# tree as-is so it is still present for a full etcupdate run after the 1772259960Sjhb# world install is complete. Instead, it installs a few critical files 1773259960Sjhb# into a separate tree. 1774259960Sjhbif [ -n "$preworld" ]; then 1775259960Sjhb OLDTREE=$NEWTREE 1776259960Sjhb NEWTREE=$WORKDIR/preworld 1777259960Sjhbfi 1778259960Sjhb 1779238423Sjhb# Open the log file. Don't truncate it if doing a minor operation so 1780238423Sjhb# that a minor operation doesn't lose log info from a major operation. 1781238423Sjhbif ! mkdir -p $WORKDIR 2>/dev/null; then 1782238423Sjhb echo "Failed to create work directory $WORKDIR" 1783238423Sjhbfi 1784238423Sjhb 1785238423Sjhbcase $command in 1786238423Sjhb diff|resolve|status) 1787238423Sjhb exec 3>>$LOGFILE 1788238423Sjhb ;; 1789238423Sjhb *) 1790238423Sjhb exec 3>$LOGFILE 1791238423Sjhb ;; 1792238423Sjhbesac 1793238423Sjhb 1794238423Sjhb${command}_cmd "$@" 1795