1238423Sjhb#!/bin/sh 2238423Sjhb# 3281887Sjhb# 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: releng/11.0/usr.sbin/etcupdate/etcupdate.sh 302912 2016-07-15 19:58:05Z bdrewery $ 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 64258066Sjhbusage: etcupdate [-npBF] [-d workdir] [-r | -s source | -t tarball] 65258066Sjhb [-A patterns] [-D destdir] [-I patterns] [-L logfile] 66258066Sjhb [-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] 72258066Sjhb 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{ 185258066Sjhb local destdir dir file make 186238423Sjhb 187302912Sbdrewery make="make $MAKE_OPTIONS -DNO_FILEMON" 188238423Sjhb 189238423Sjhb log "Building tree at $1 with $make" 190238423Sjhb mkdir -p $1/usr/obj >&3 2>&1 191258066Sjhb destdir=`realpath $1` 192238423Sjhb 193258066Sjhb if [ -n "$preworld" ]; then 194258066Sjhb # Build a limited tree that only contains files that are 195258066Sjhb # crucial to installworld. 196258066Sjhb for file in $PREWORLD_FILES; do 197258066Sjhb dir=`dirname /$file` 198258066Sjhb mkdir -p $1/$dir >&3 2>&1 || return 1 199258066Sjhb cp -p $SRCDIR/$file $1/$file || return 1 200258066Sjhb done 201258066Sjhb elif ! [ -n "$nobuild" ]; then 202258066Sjhb (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs && 203258066Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make _obj SUBDIR_OVERRIDE=etc && 204258066Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make everything SUBDIR_OVERRIDE=etc && 205258066Sjhb MAKEOBJDIRPREFIX=$destdir/usr/obj $make DESTDIR=$destdir distribution) \ 206238423Sjhb >&3 2>&1 || return 1 207238423Sjhb else 208258066Sjhb (cd $SRCDIR; $make DESTDIR=$destdir distrib-dirs && 209258066Sjhb $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. 216261031Sjhb rm -f $1/etc/*.db $1/etc/passwd $1/var/db/services.db >&3 2>&1 || \ 217261031Sjhb return 1 218238423Sjhb 219238423Sjhb # Remove empty files. These just clutter the output of 'diff'. 220238423Sjhb find $1 -type f -size 0 -delete >&3 2>&1 || return 1 221238423Sjhb 222238423Sjhb # Trim empty directories. 223238423Sjhb find -d $1 -type d -empty -delete >&3 2>&1 || return 1 224238423Sjhb return 0 225238423Sjhb} 226238423Sjhb 227238423Sjhb# Generate a new NEWTREE tree. If tarball is set, then the tree is 228238423Sjhb# extracted from the tarball. Otherwise the tree is built from a 229238423Sjhb# source tree. 230238423Sjhbextract_tree() 231238423Sjhb{ 232258066Sjhb local files 233258066Sjhb 234238423Sjhb # If we have a tarball, extract that into the new directory. 235238423Sjhb if [ -n "$tarball" ]; then 236258066Sjhb files= 237258066Sjhb if [ -n "$preworld" ]; then 238258066Sjhb files="$PREWORLD_FILES" 239258066Sjhb fi 240258066Sjhb if ! (mkdir -p $NEWTREE && tar xf $tarball -C $NEWTREE $files) \ 241238423Sjhb >&3 2>&1; then 242238423Sjhb echo "Failed to extract new tree." 243238423Sjhb remove_tree $NEWTREE 244238423Sjhb exit 1 245238423Sjhb fi 246238423Sjhb else 247238423Sjhb if ! build_tree $NEWTREE; then 248238423Sjhb echo "Failed to build new tree." 249238423Sjhb remove_tree $NEWTREE 250238423Sjhb exit 1 251238423Sjhb fi 252238423Sjhb fi 253238423Sjhb} 254238423Sjhb 255238423Sjhb# Forcefully remove a tree. Returns true (0) if the operation succeeds. 256238423Sjhb# 257238423Sjhb# $1 - path to tree 258238423Sjhbremove_tree() 259238423Sjhb{ 260238423Sjhb 261238423Sjhb rm -rf $1 >&3 2>&1 262238423Sjhb if [ -e $1 ]; then 263238423Sjhb chflags -R noschg $1 >&3 2>&1 264238423Sjhb rm -rf $1 >&3 2>&1 265238423Sjhb fi 266238423Sjhb [ ! -e $1 ] 267238423Sjhb} 268238423Sjhb 269238423Sjhb# Return values for compare() 270238423SjhbCOMPARE_EQUAL=0 271238423SjhbCOMPARE_ONLYFIRST=1 272238423SjhbCOMPARE_ONLYSECOND=2 273238423SjhbCOMPARE_DIFFTYPE=3 274238423SjhbCOMPARE_DIFFLINKS=4 275238423SjhbCOMPARE_DIFFFILES=5 276238423Sjhb 277238423Sjhb# Compare two files/directories/symlinks. Note that this does not 278238423Sjhb# recurse into subdirectories. Instead, if two nodes are both 279238423Sjhb# directories, they are assumed to be equivalent. 280238423Sjhb# 281238423Sjhb# Returns true (0) if the nodes are identical. If only one of the two 282238423Sjhb# nodes are present, return one of the COMPARE_ONLY* constants. If 283238423Sjhb# the nodes are different, return one of the COMPARE_DIFF* constants 284238423Sjhb# to indicate the type of difference. 285238423Sjhb# 286238423Sjhb# $1 - first node 287238423Sjhb# $2 - second node 288238423Sjhbcompare() 289238423Sjhb{ 290238423Sjhb local first second 291238423Sjhb 292238423Sjhb # If the first node doesn't exist, then check for the second 293238423Sjhb # node. Note that -e will fail for a symbolic link that 294238423Sjhb # points to a missing target. 295238423Sjhb if ! exists $1; then 296238423Sjhb if exists $2; then 297238423Sjhb return $COMPARE_ONLYSECOND 298238423Sjhb else 299238423Sjhb return $COMPARE_EQUAL 300238423Sjhb fi 301238423Sjhb elif ! exists $2; then 302238423Sjhb return $COMPARE_ONLYFIRST 303238423Sjhb fi 304238423Sjhb 305238423Sjhb # If the two nodes are different file types fail. 306238423Sjhb first=`stat -f "%Hp" $1` 307238423Sjhb second=`stat -f "%Hp" $2` 308238423Sjhb if [ "$first" != "$second" ]; then 309238423Sjhb return $COMPARE_DIFFTYPE 310238423Sjhb fi 311238423Sjhb 312238423Sjhb # If both are symlinks, compare the link values. 313238423Sjhb if [ -L $1 ]; then 314238423Sjhb first=`readlink $1` 315238423Sjhb second=`readlink $2` 316238423Sjhb if [ "$first" = "$second" ]; then 317238423Sjhb return $COMPARE_EQUAL 318238423Sjhb else 319238423Sjhb return $COMPARE_DIFFLINKS 320238423Sjhb fi 321238423Sjhb fi 322238423Sjhb 323238423Sjhb # If both are files, compare the file contents. 324238423Sjhb if [ -f $1 ]; then 325238423Sjhb if cmp -s $1 $2; then 326238423Sjhb return $COMPARE_EQUAL 327238423Sjhb else 328238423Sjhb return $COMPARE_DIFFFILES 329238423Sjhb fi 330238423Sjhb fi 331238423Sjhb 332238423Sjhb # As long as the two nodes are the same type of file, consider 333238423Sjhb # them equivalent. 334238423Sjhb return $COMPARE_EQUAL 335238423Sjhb} 336238423Sjhb 337238423Sjhb# Returns true (0) if the only difference between two regular files is a 338238423Sjhb# change in the FreeBSD ID string. 339238423Sjhb# 340238423Sjhb# $1 - path of first file 341238423Sjhb# $2 - path of second file 342238423Sjhbfbsdid_only() 343238423Sjhb{ 344238423Sjhb 345238423Sjhb diff -qI '\$FreeBSD.*\$' $1 $2 >/dev/null 2>&1 346238423Sjhb} 347238423Sjhb 348238423Sjhb# This is a wrapper around compare that will return COMPARE_EQUAL if 349238423Sjhb# the only difference between two regular files is a change in the 350238423Sjhb# FreeBSD ID string. It only makes this adjustment if the -F flag has 351238423Sjhb# been specified. 352238423Sjhb# 353238423Sjhb# $1 - first node 354238423Sjhb# $2 - second node 355238423Sjhbcompare_fbsdid() 356238423Sjhb{ 357238423Sjhb local cmp 358238423Sjhb 359238423Sjhb compare $1 $2 360238423Sjhb cmp=$? 361238423Sjhb 362238423Sjhb if [ -n "$FREEBSD_ID" -a "$cmp" -eq $COMPARE_DIFFFILES ] && \ 363238423Sjhb fbsdid_only $1 $2; then 364238423Sjhb return $COMPARE_EQUAL 365238423Sjhb fi 366238423Sjhb 367238423Sjhb return $cmp 368238423Sjhb} 369238423Sjhb 370238423Sjhb# Returns true (0) if a directory is empty. 371238423Sjhb# 372238423Sjhb# $1 - pathname of the directory to check 373238423Sjhbempty_dir() 374238423Sjhb{ 375238423Sjhb local contents 376238423Sjhb 377238423Sjhb contents=`ls -A $1` 378238423Sjhb [ -z "$contents" ] 379238423Sjhb} 380238423Sjhb 381238423Sjhb# Returns true (0) if one directories contents are a subset of the 382238423Sjhb# other. This will recurse to handle subdirectories and compares 383238423Sjhb# individual files in the trees. Its purpose is to quiet spurious 384238423Sjhb# directory warnings for dryrun invocations. 385238423Sjhb# 386238423Sjhb# $1 - first directory (sub) 387238423Sjhb# $2 - second directory (super) 388238423Sjhbdir_subset() 389238423Sjhb{ 390238423Sjhb local contents file 391238423Sjhb 392238423Sjhb if ! [ -d $1 -a -d $2 ]; then 393238423Sjhb return 1 394238423Sjhb fi 395238423Sjhb 396238423Sjhb # Ignore files that are present in the second directory but not 397238423Sjhb # in the first. 398238423Sjhb contents=`ls -A $1` 399238423Sjhb for file in $contents; do 400238423Sjhb if ! compare $1/$file $2/$file; then 401238423Sjhb return 1 402238423Sjhb fi 403238423Sjhb 404238423Sjhb if [ -d $1/$file ]; then 405238423Sjhb if ! dir_subset $1/$file $2/$file; then 406238423Sjhb return 1 407238423Sjhb fi 408238423Sjhb fi 409238423Sjhb done 410238423Sjhb return 0 411238423Sjhb} 412238423Sjhb 413238423Sjhb# Returns true (0) if a directory in the destination tree is empty. 414238423Sjhb# If this is a dryrun, then this returns true as long as the contents 415238423Sjhb# of the directory are a subset of the contents in the old tree 416238423Sjhb# (meaning that the directory would be empty in a non-dryrun when this 417238423Sjhb# was invoked) to quiet spurious warnings. 418238423Sjhb# 419238423Sjhb# $1 - pathname of the directory to check relative to DESTDIR. 420238423Sjhbempty_destdir() 421238423Sjhb{ 422238423Sjhb 423238423Sjhb if [ -n "$dryrun" ]; then 424238423Sjhb dir_subset $DESTDIR/$1 $OLDTREE/$1 425238423Sjhb return 426238423Sjhb fi 427238423Sjhb 428238423Sjhb empty_dir $DESTDIR/$1 429238423Sjhb} 430238423Sjhb 431238423Sjhb# Output a diff of two directory entries with the same relative name 432238423Sjhb# in different trees. Note that as with compare(), this does not 433238423Sjhb# recurse into subdirectories. If the nodes are identical, nothing is 434238423Sjhb# output. 435238423Sjhb# 436238423Sjhb# $1 - first tree 437238423Sjhb# $2 - second tree 438238423Sjhb# $3 - node name 439238423Sjhb# $4 - label for first tree 440238423Sjhb# $5 - label for second tree 441238423Sjhbdiffnode() 442238423Sjhb{ 443238423Sjhb local first second file old new diffargs 444238423Sjhb 445238423Sjhb if [ -n "$FREEBSD_ID" ]; then 446238423Sjhb diffargs="-I \\\$FreeBSD.*\\\$" 447238423Sjhb else 448238423Sjhb diffargs="" 449238423Sjhb fi 450238423Sjhb 451238423Sjhb compare_fbsdid $1/$3 $2/$3 452238423Sjhb case $? in 453238423Sjhb $COMPARE_EQUAL) 454238423Sjhb ;; 455238423Sjhb $COMPARE_ONLYFIRST) 456238423Sjhb echo 457238423Sjhb echo "Removed: $3" 458238423Sjhb echo 459238423Sjhb ;; 460238423Sjhb $COMPARE_ONLYSECOND) 461238423Sjhb echo 462238423Sjhb echo "Added: $3" 463238423Sjhb echo 464238423Sjhb ;; 465238423Sjhb $COMPARE_DIFFTYPE) 466238423Sjhb first=`file_type $1/$3` 467238423Sjhb second=`file_type $2/$3` 468238423Sjhb echo 469238423Sjhb echo "Node changed from a $first to a $second: $3" 470238423Sjhb echo 471238423Sjhb ;; 472238423Sjhb $COMPARE_DIFFLINKS) 473238423Sjhb first=`readlink $1/$file` 474238423Sjhb second=`readlink $2/$file` 475238423Sjhb echo 476238423Sjhb echo "Link changed: $file" 477238423Sjhb rule "=" 478238423Sjhb echo "-$first" 479238423Sjhb echo "+$second" 480238423Sjhb echo 481238423Sjhb ;; 482238423Sjhb $COMPARE_DIFFFILES) 483238423Sjhb echo "Index: $3" 484238423Sjhb rule "=" 485238423Sjhb diff -u $diffargs -L "$3 ($4)" $1/$3 -L "$3 ($5)" $2/$3 486238423Sjhb ;; 487238423Sjhb esac 488238423Sjhb} 489238423Sjhb 490259134Sjhb# Run one-off commands after an update has completed. These commands 491259134Sjhb# are not tied to a specific file, so they cannot be handled by 492259134Sjhb# post_install_file(). 493259134Sjhbpost_update() 494259134Sjhb{ 495259134Sjhb local args 496259134Sjhb 497259134Sjhb # None of these commands should be run for a pre-world update. 498259134Sjhb if [ -n "$preworld" ]; then 499259134Sjhb return 500259134Sjhb fi 501259134Sjhb 502259134Sjhb # If /etc/localtime exists and is not a symlink and /var/db/zoneinfo 503259134Sjhb # exists, run tzsetup -r to refresh /etc/localtime. 504259134Sjhb if [ -f ${DESTDIR}/etc/localtime -a \ 505259134Sjhb ! -L ${DESTDIR}/etc/localtime ]; then 506259134Sjhb if [ -f ${DESTDIR}/var/db/zoneinfo ]; then 507259134Sjhb if [ -n "${DESTDIR}" ]; then 508259134Sjhb args="-C ${DESTDIR}" 509259134Sjhb else 510259134Sjhb args="" 511259134Sjhb fi 512259134Sjhb log "tzsetup -r ${args}" 513259134Sjhb if [ -z "$dryrun" ]; then 514259134Sjhb tzsetup -r ${args} >&3 2>&1 515259134Sjhb fi 516259134Sjhb else 517259134Sjhb warn "Needs update: /etc/localtime (required" \ 518296204Strasz "manual update via tzsetup(8))" 519259134Sjhb fi 520259134Sjhb fi 521259134Sjhb} 522259134Sjhb 523238423Sjhb# Create missing parent directories of a node in a target tree 524238423Sjhb# preserving the owner, group, and permissions from a specified 525238423Sjhb# template tree. 526238423Sjhb# 527238423Sjhb# $1 - template tree 528238423Sjhb# $2 - target tree 529238423Sjhb# $3 - pathname of the node (relative to both trees) 530238423Sjhbinstall_dirs() 531238423Sjhb{ 532238423Sjhb local args dir 533238423Sjhb 534238423Sjhb dir=`dirname $3` 535238423Sjhb 536238423Sjhb # Nothing to do if the parent directory exists. This also 537238423Sjhb # catches the degenerate cases when the path is just a simple 538238423Sjhb # filename. 539238423Sjhb if [ -d ${2}$dir ]; then 540238423Sjhb return 0 541238423Sjhb fi 542238423Sjhb 543238423Sjhb # If non-directory file exists with the desired directory 544238423Sjhb # name, then fail. 545238423Sjhb if exists ${2}$dir; then 546238423Sjhb # If this is a dryrun and we are installing the 547238423Sjhb # directory in the DESTDIR and the file in the DESTDIR 548238423Sjhb # matches the file in the old tree, then fake success 549238423Sjhb # to quiet spurious warnings. 550238423Sjhb if [ -n "$dryrun" -a "$2" = "$DESTDIR" ]; then 551238423Sjhb if compare $OLDTREE/$dir $DESTDIR/$dir; then 552238423Sjhb return 0 553238423Sjhb fi 554238423Sjhb fi 555238423Sjhb 556238423Sjhb args=`file_type ${2}$dir` 557238423Sjhb warn "Directory mismatch: ${2}$dir ($args)" 558238423Sjhb return 1 559238423Sjhb fi 560238423Sjhb 561238423Sjhb # Ensure the parent directory of the directory is present 562238423Sjhb # first. 563238423Sjhb if ! install_dirs $1 "$2" $dir; then 564238423Sjhb return 1 565238423Sjhb fi 566238423Sjhb 567238423Sjhb # Format attributes from template directory as install(1) 568238423Sjhb # arguments. 569238423Sjhb args=`stat -f "-o %Su -g %Sg -m %0Mp%0Lp" $1/$dir` 570238423Sjhb 571238423Sjhb log "install -d $args ${2}$dir" 572238423Sjhb if [ -z "$dryrun" ]; then 573238423Sjhb install -d $args ${2}$dir >&3 2>&1 574238423Sjhb fi 575238423Sjhb return 0 576238423Sjhb} 577238423Sjhb 578238423Sjhb# Perform post-install fixups for a file. This largely consists of 579238423Sjhb# regenerating any files that depend on the newly installed file. 580238423Sjhb# 581238423Sjhb# $1 - pathname of the updated file (relative to DESTDIR) 582238423Sjhbpost_install_file() 583238423Sjhb{ 584238423Sjhb case $1 in 585238423Sjhb /etc/mail/aliases) 586238423Sjhb # Grr, newaliases only works for an empty DESTDIR. 587238423Sjhb if [ -z "$DESTDIR" ]; then 588238423Sjhb log "newaliases" 589238423Sjhb if [ -z "$dryrun" ]; then 590238423Sjhb newaliases >&3 2>&1 591238423Sjhb fi 592238423Sjhb else 593238423Sjhb NEWALIAS_WARN=yes 594238423Sjhb fi 595238423Sjhb ;; 596238423Sjhb /etc/login.conf) 597238423Sjhb log "cap_mkdb ${DESTDIR}$1" 598238423Sjhb if [ -z "$dryrun" ]; then 599238423Sjhb cap_mkdb ${DESTDIR}$1 >&3 2>&1 600238423Sjhb fi 601238423Sjhb ;; 602238423Sjhb /etc/master.passwd) 603238423Sjhb log "pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1" 604238423Sjhb if [ -z "$dryrun" ]; then 605238423Sjhb pwd_mkdb -p -d $DESTDIR/etc ${DESTDIR}$1 \ 606238423Sjhb >&3 2>&1 607238423Sjhb fi 608238423Sjhb ;; 609238423Sjhb /etc/motd) 610238423Sjhb # /etc/rc.d/motd hardcodes the /etc/motd path. 611238423Sjhb # Don't warn about non-empty DESTDIR's since this 612238423Sjhb # change is only cosmetic anyway. 613238423Sjhb if [ -z "$DESTDIR" ]; then 614238423Sjhb log "sh /etc/rc.d/motd start" 615238423Sjhb if [ -z "$dryrun" ]; then 616238423Sjhb sh /etc/rc.d/motd start >&3 2>&1 617238423Sjhb fi 618238423Sjhb fi 619238423Sjhb ;; 620259134Sjhb /etc/services) 621259134Sjhb log "services_mkdb -q -o $DESTDIR/var/db/services.db" \ 622259134Sjhb "${DESTDIR}$1" 623259134Sjhb if [ -z "$dryrun" ]; then 624259134Sjhb services_mkdb -q -o $DESTDIR/var/db/services.db \ 625259134Sjhb ${DESTDIR}$1 >&3 2>&1 626259134Sjhb fi 627259134Sjhb ;; 628238423Sjhb esac 629238423Sjhb} 630238423Sjhb 631238423Sjhb# Install the "new" version of a file. Returns true if it succeeds 632238423Sjhb# and false otherwise. 633238423Sjhb# 634238423Sjhb# $1 - pathname of the file to install (relative to DESTDIR) 635238423Sjhbinstall_new() 636238423Sjhb{ 637238423Sjhb 638238423Sjhb if ! install_dirs $NEWTREE "$DESTDIR" $1; then 639238423Sjhb return 1 640238423Sjhb fi 641238423Sjhb log "cp -Rp ${NEWTREE}$1 ${DESTDIR}$1" 642238423Sjhb if [ -z "$dryrun" ]; then 643238423Sjhb cp -Rp ${NEWTREE}$1 ${DESTDIR}$1 >&3 2>&1 644238423Sjhb fi 645238423Sjhb post_install_file $1 646238423Sjhb return 0 647238423Sjhb} 648238423Sjhb 649238423Sjhb# Install the "resolved" version of a file. Returns true if it succeeds 650238423Sjhb# and false otherwise. 651238423Sjhb# 652238423Sjhb# $1 - pathname of the file to install (relative to DESTDIR) 653238423Sjhbinstall_resolved() 654238423Sjhb{ 655238423Sjhb 656238423Sjhb # This should always be present since the file is already 657238423Sjhb # there (it caused a conflict). However, it doesn't hurt to 658238423Sjhb # just be safe. 659238423Sjhb if ! install_dirs $NEWTREE "$DESTDIR" $1; then 660238423Sjhb return 1 661238423Sjhb fi 662238423Sjhb 663238423Sjhb log "cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1" 664238423Sjhb cp -Rp ${CONFLICTS}$1 ${DESTDIR}$1 >&3 2>&1 665238423Sjhb post_install_file $1 666238423Sjhb return 0 667238423Sjhb} 668238423Sjhb 669238423Sjhb# Generate a conflict file when a "new" file conflicts with an 670238423Sjhb# existing file in DESTDIR. 671238423Sjhb# 672238423Sjhb# $1 - pathname of the file that conflicts (relative to DESTDIR) 673238423Sjhbnew_conflict() 674238423Sjhb{ 675238423Sjhb 676238423Sjhb if [ -n "$dryrun" ]; then 677238423Sjhb return 678238423Sjhb fi 679238423Sjhb 680238423Sjhb install_dirs $NEWTREE $CONFLICTS $1 681238423Sjhb diff --changed-group-format='<<<<<<< (local) 682238423Sjhb%<======= 683238423Sjhb%>>>>>>>> (stock) 684238423Sjhb' $DESTDIR/$1 $NEWTREE/$1 > $CONFLICTS/$1 685238423Sjhb} 686238423Sjhb 687238423Sjhb# Remove the "old" version of a file. 688238423Sjhb# 689238423Sjhb# $1 - pathname of the old file to remove (relative to DESTDIR) 690238423Sjhbremove_old() 691238423Sjhb{ 692238423Sjhb log "rm -f ${DESTDIR}$1" 693238423Sjhb if [ -z "$dryrun" ]; then 694238423Sjhb rm -f ${DESTDIR}$1 >&3 2>&1 695238423Sjhb fi 696238423Sjhb echo " D $1" 697238423Sjhb} 698238423Sjhb 699238423Sjhb# Update a file that has no local modifications. 700238423Sjhb# 701238423Sjhb# $1 - pathname of the file to update (relative to DESTDIR) 702238423Sjhbupdate_unmodified() 703238423Sjhb{ 704238423Sjhb local new old 705238423Sjhb 706238423Sjhb # If the old file is a directory, then remove it with rmdir 707238423Sjhb # (this should only happen if the file has changed its type 708238423Sjhb # from a directory to a non-directory). If the directory 709238423Sjhb # isn't empty, then fail. This will be reported as a warning 710238423Sjhb # later. 711238423Sjhb if [ -d $DESTDIR/$1 ]; then 712238423Sjhb if empty_destdir $1; then 713238423Sjhb log "rmdir ${DESTDIR}$1" 714238423Sjhb if [ -z "$dryrun" ]; then 715238423Sjhb rmdir ${DESTDIR}$1 >&3 2>&1 716238423Sjhb fi 717238423Sjhb else 718238423Sjhb return 1 719238423Sjhb fi 720238423Sjhb 721238423Sjhb # If both the old and new files are regular files, leave the 722238423Sjhb # existing file. This avoids breaking hard links for /.cshrc 723238423Sjhb # and /.profile. Otherwise, explicitly remove the old file. 724238423Sjhb elif ! [ -f ${DESTDIR}$1 -a -f ${NEWTREE}$1 ]; then 725238423Sjhb log "rm -f ${DESTDIR}$1" 726238423Sjhb if [ -z "$dryrun" ]; then 727238423Sjhb rm -f ${DESTDIR}$1 >&3 2>&1 728238423Sjhb fi 729238423Sjhb fi 730238423Sjhb 731238423Sjhb # If the new file is a directory, note that the old file has 732238423Sjhb # been removed, but don't do anything else for now. The 733238423Sjhb # directory will be installed if needed when new files within 734238423Sjhb # that directory are installed. 735238423Sjhb if [ -d $NEWTREE/$1 ]; then 736238423Sjhb if empty_dir $NEWTREE/$1; then 737238423Sjhb echo " D $file" 738238423Sjhb else 739238423Sjhb echo " U $file" 740238423Sjhb fi 741238423Sjhb elif install_new $1; then 742238423Sjhb echo " U $file" 743238423Sjhb fi 744238423Sjhb return 0 745238423Sjhb} 746238423Sjhb 747238423Sjhb# Update the FreeBSD ID string in a locally modified file to match the 748238423Sjhb# FreeBSD ID string from the "new" version of the file. 749238423Sjhb# 750238423Sjhb# $1 - pathname of the file to update (relative to DESTDIR) 751238423Sjhbupdate_freebsdid() 752238423Sjhb{ 753238423Sjhb local new dest file 754238423Sjhb 755238423Sjhb # If the FreeBSD ID string is removed from the local file, 756238423Sjhb # there is nothing to do. In this case, treat the file as 757238423Sjhb # updated. Otherwise, if either file has more than one 758238423Sjhb # FreeBSD ID string, just punt and let the user handle the 759238423Sjhb # conflict manually. 760238423Sjhb new=`grep -c '\$FreeBSD.*\$' ${NEWTREE}$1` 761238423Sjhb dest=`grep -c '\$FreeBSD.*\$' ${DESTDIR}$1` 762238423Sjhb if [ "$dest" -eq 0 ]; then 763238423Sjhb return 0 764238423Sjhb fi 765238423Sjhb if [ "$dest" -ne 1 -o "$dest" -ne 1 ]; then 766238423Sjhb return 1 767238423Sjhb fi 768238423Sjhb 769238423Sjhb # If the FreeBSD ID string in the new file matches the FreeBSD ID 770238423Sjhb # string in the local file, there is nothing to do. 771238423Sjhb new=`grep '\$FreeBSD.*\$' ${NEWTREE}$1` 772238423Sjhb dest=`grep '\$FreeBSD.*\$' ${DESTDIR}$1` 773238423Sjhb if [ "$new" = "$dest" ]; then 774238423Sjhb return 0 775238423Sjhb fi 776238423Sjhb 777238423Sjhb # Build the new file in three passes. First, copy all the 778238423Sjhb # lines preceding the FreeBSD ID string from the local version 779238423Sjhb # of the file. Second, append the FreeBSD ID string line from 780238423Sjhb # the new version. Finally, append all the lines after the 781238423Sjhb # FreeBSD ID string from the local version of the file. 782238423Sjhb file=`mktemp $WORKDIR/etcupdate-XXXXXXX` 783238423Sjhb awk '/\$FreeBSD.*\$/ { exit } { print }' ${DESTDIR}$1 >> $file 784238423Sjhb awk '/\$FreeBSD.*\$/ { print }' ${NEWTREE}$1 >> $file 785238423Sjhb awk '/\$FreeBSD.*\$/ { ok = 1; next } { if (ok) print }' \ 786238423Sjhb ${DESTDIR}$1 >> $file 787238423Sjhb 788238423Sjhb # As an extra sanity check, fail the attempt if the updated 789238423Sjhb # version of the file has any differences aside from the 790238423Sjhb # FreeBSD ID string. 791238423Sjhb if ! fbsdid_only ${DESTDIR}$1 $file; then 792238423Sjhb rm -f $file 793238423Sjhb return 1 794238423Sjhb fi 795238423Sjhb 796238423Sjhb log "cp $file ${DESTDIR}$1" 797238423Sjhb if [ -z "$dryrun" ]; then 798238423Sjhb cp $file ${DESTDIR}$1 >&3 2>&1 799238423Sjhb fi 800238423Sjhb rm -f $file 801238423Sjhb post_install_file $1 802238423Sjhb echo " M $1" 803238423Sjhb return 0 804238423Sjhb} 805238423Sjhb 806238423Sjhb# Attempt to update a file that has local modifications. This routine 807238423Sjhb# only handles regular files. If the 3-way merge succeeds without 808238423Sjhb# conflicts, the updated file is installed. If the merge fails, the 809238423Sjhb# merged version with conflict markers is left in the CONFLICTS tree. 810238423Sjhb# 811238423Sjhb# $1 - pathname of the file to merge (relative to DESTDIR) 812238423Sjhbmerge_file() 813238423Sjhb{ 814238423Sjhb local res 815238423Sjhb 816238423Sjhb # Try the merge to see if there is a conflict. 817238423Sjhb merge -q -p ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 >/dev/null 2>&3 818238423Sjhb res=$? 819238423Sjhb case $res in 820238423Sjhb 0) 821238423Sjhb # No conflicts, so just redo the merge to the 822238423Sjhb # real file. 823238423Sjhb log "merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1" 824238423Sjhb if [ -z "$dryrun" ]; then 825238423Sjhb merge ${DESTDIR}$1 ${OLDTREE}$1 ${NEWTREE}$1 826238423Sjhb fi 827238423Sjhb post_install_file $1 828238423Sjhb echo " M $1" 829238423Sjhb ;; 830238423Sjhb 1) 831238423Sjhb # Conflicts, save a version with conflict markers in 832238423Sjhb # the conflicts directory. 833238423Sjhb if [ -z "$dryrun" ]; then 834238423Sjhb install_dirs $NEWTREE $CONFLICTS $1 835238423Sjhb log "cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1" 836238423Sjhb cp -Rp ${DESTDIR}$1 ${CONFLICTS}$1 >&3 2>&1 837238423Sjhb merge -A -q -L "yours" -L "original" -L "new" \ 838238423Sjhb ${CONFLICTS}$1 ${OLDTREE}$1 ${NEWTREE}$1 839238423Sjhb fi 840238423Sjhb echo " C $1" 841238423Sjhb ;; 842238423Sjhb *) 843238423Sjhb panic "merge failed with status $res" 844238423Sjhb ;; 845238423Sjhb esac 846238423Sjhb} 847238423Sjhb 848238423Sjhb# Returns true if a file contains conflict markers from a merge conflict. 849238423Sjhb# 850238423Sjhb# $1 - pathname of the file to resolve (relative to DESTDIR) 851238423Sjhbhas_conflicts() 852238423Sjhb{ 853238423Sjhb 854238423Sjhb egrep -q '^(<{7}|\|{7}|={7}|>{7}) ' $CONFLICTS/$1 855238423Sjhb} 856238423Sjhb 857238423Sjhb# Attempt to resolve a conflict. The user is prompted to choose an 858238423Sjhb# action for each conflict. If the user edits the file, they are 859238423Sjhb# prompted again for an action. The process is very similar to 860238423Sjhb# resolving conflicts after an update or merge with Perforce or 861238423Sjhb# Subversion. The prompts are modelled on a subset of the available 862238423Sjhb# commands for resolving conflicts with Subversion. 863238423Sjhb# 864238423Sjhb# $1 - pathname of the file to resolve (relative to DESTDIR) 865238423Sjhbresolve_conflict() 866238423Sjhb{ 867238423Sjhb local command junk 868238423Sjhb 869238423Sjhb echo "Resolving conflict in '$1':" 870238423Sjhb edit= 871238423Sjhb while true; do 872238423Sjhb # Only display the resolved command if the file 873238423Sjhb # doesn't contain any conflicts. 874238423Sjhb echo -n "Select: (p) postpone, (df) diff-full, (e) edit," 875238423Sjhb if ! has_conflicts $1; then 876238423Sjhb echo -n " (r) resolved," 877238423Sjhb fi 878238423Sjhb echo 879238423Sjhb echo -n " (h) help for more options: " 880238423Sjhb read command 881238423Sjhb case $command in 882238423Sjhb df) 883238423Sjhb diff -u ${DESTDIR}$1 ${CONFLICTS}$1 884238423Sjhb ;; 885238423Sjhb e) 886238423Sjhb $EDITOR ${CONFLICTS}$1 887238423Sjhb ;; 888238423Sjhb h) 889238423Sjhb cat <<EOF 890238423Sjhb (p) postpone - ignore this conflict for now 891238423Sjhb (df) diff-full - show all changes made to merged file 892238423Sjhb (e) edit - change merged file in an editor 893238423Sjhb (r) resolved - accept merged version of file 894238423Sjhb (mf) mine-full - accept local version of entire file (ignore new changes) 895238423Sjhb (tf) theirs-full - accept new version of entire file (lose local changes) 896238423Sjhb (h) help - show this list 897238423SjhbEOF 898238423Sjhb ;; 899238423Sjhb mf) 900238423Sjhb # For mine-full, just delete the 901238423Sjhb # merged file and leave the local 902238423Sjhb # version of the file as-is. 903238423Sjhb rm ${CONFLICTS}$1 904238423Sjhb return 905238423Sjhb ;; 906238423Sjhb p) 907238423Sjhb return 908238423Sjhb ;; 909238423Sjhb r) 910238423Sjhb # If the merged file has conflict 911238423Sjhb # markers, require confirmation. 912238423Sjhb if has_conflicts $1; then 913238423Sjhb echo "File '$1' still has conflicts," \ 914238423Sjhb "are you sure? (y/n) " 915238423Sjhb read junk 916238423Sjhb if [ "$junk" != "y" ]; then 917238423Sjhb continue 918238423Sjhb fi 919238423Sjhb fi 920238423Sjhb 921238423Sjhb if ! install_resolved $1; then 922238423Sjhb panic "Unable to install merged" \ 923238423Sjhb "version of $1" 924238423Sjhb fi 925238423Sjhb rm ${CONFLICTS}$1 926238423Sjhb return 927238423Sjhb ;; 928238423Sjhb tf) 929238423Sjhb # For theirs-full, install the new 930238423Sjhb # version of the file over top of the 931238423Sjhb # existing file. 932238423Sjhb if ! install_new $1; then 933238423Sjhb panic "Unable to install new" \ 934238423Sjhb "version of $1" 935238423Sjhb fi 936238423Sjhb rm ${CONFLICTS}$1 937238423Sjhb return 938238423Sjhb ;; 939238423Sjhb *) 940238423Sjhb echo "Invalid command." 941238423Sjhb ;; 942238423Sjhb esac 943238423Sjhb done 944238423Sjhb} 945238423Sjhb 946238423Sjhb# Handle a file that has been removed from the new tree. If the file 947238423Sjhb# does not exist in DESTDIR, then there is nothing to do. If the file 948238423Sjhb# exists in DESTDIR and is identical to the old version, remove it 949238423Sjhb# from DESTDIR. Otherwise, whine about the conflict but leave the 950238423Sjhb# file in DESTDIR. To handle directories, this uses two passes. The 951238423Sjhb# first pass handles all non-directory files. The second pass handles 952238423Sjhb# just directories and removes them if they are empty. 953238423Sjhb# 954238423Sjhb# If -F is specified, and the only difference in the file in DESTDIR 955238423Sjhb# is a change in the FreeBSD ID string, then remove the file. 956238423Sjhb# 957238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 958238423Sjhbhandle_removed_file() 959238423Sjhb{ 960238423Sjhb local dest file 961238423Sjhb 962238423Sjhb file=$1 963238423Sjhb if ignore $file; then 964238423Sjhb log "IGNORE: removed file $file" 965238423Sjhb return 966238423Sjhb fi 967238423Sjhb 968238423Sjhb compare_fbsdid $DESTDIR/$file $OLDTREE/$file 969238423Sjhb case $? in 970238423Sjhb $COMPARE_EQUAL) 971238423Sjhb if ! [ -d $DESTDIR/$file ]; then 972238423Sjhb remove_old $file 973238423Sjhb fi 974238423Sjhb ;; 975238423Sjhb $COMPARE_ONLYFIRST) 976238423Sjhb panic "Removed file now missing" 977238423Sjhb ;; 978238423Sjhb $COMPARE_ONLYSECOND) 979238423Sjhb # Already removed, nothing to do. 980238423Sjhb ;; 981238423Sjhb $COMPARE_DIFFTYPE|$COMPARE_DIFFLINKS|$COMPARE_DIFFFILES) 982238423Sjhb dest=`file_type $DESTDIR/$file` 983238423Sjhb warn "Modified $dest remains: $file" 984238423Sjhb ;; 985238423Sjhb esac 986238423Sjhb} 987238423Sjhb 988238423Sjhb# Handle a directory that has been removed from the new tree. Only 989238423Sjhb# remove the directory if it is empty. 990238423Sjhb# 991238423Sjhb# $1 - pathname of the directory (relative to DESTDIR) 992238423Sjhbhandle_removed_directory() 993238423Sjhb{ 994238423Sjhb local dir 995238423Sjhb 996238423Sjhb dir=$1 997238423Sjhb if ignore $dir; then 998238423Sjhb log "IGNORE: removed dir $dir" 999238423Sjhb return 1000238423Sjhb fi 1001238423Sjhb 1002238423Sjhb if [ -d $DESTDIR/$dir -a -d $OLDTREE/$dir ]; then 1003238423Sjhb if empty_destdir $dir; then 1004238423Sjhb log "rmdir ${DESTDIR}$dir" 1005238423Sjhb if [ -z "$dryrun" ]; then 1006238423Sjhb rmdir ${DESTDIR}$dir >/dev/null 2>&1 1007238423Sjhb fi 1008238423Sjhb echo " D $dir" 1009238423Sjhb else 1010238423Sjhb warn "Non-empty directory remains: $dir" 1011238423Sjhb fi 1012238423Sjhb fi 1013238423Sjhb} 1014238423Sjhb 1015238423Sjhb# Handle a file that exists in both the old and new trees. If the 1016238423Sjhb# file has not changed in the old and new trees, there is nothing to 1017238423Sjhb# do. If the file in the destination directory matches the new file, 1018238423Sjhb# there is nothing to do. If the file in the destination directory 1019238423Sjhb# matches the old file, then the new file should be installed. 1020238423Sjhb# Everything else becomes some sort of conflict with more detailed 1021238423Sjhb# handling. 1022238423Sjhb# 1023238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 1024238423Sjhbhandle_modified_file() 1025238423Sjhb{ 1026238423Sjhb local cmp dest file new newdestcmp old 1027238423Sjhb 1028238423Sjhb file=$1 1029238423Sjhb if ignore $file; then 1030238423Sjhb log "IGNORE: modified file $file" 1031238423Sjhb return 1032238423Sjhb fi 1033238423Sjhb 1034238423Sjhb compare $OLDTREE/$file $NEWTREE/$file 1035238423Sjhb cmp=$? 1036238423Sjhb if [ $cmp -eq $COMPARE_EQUAL ]; then 1037238423Sjhb return 1038238423Sjhb fi 1039238423Sjhb 1040238423Sjhb if [ $cmp -eq $COMPARE_ONLYFIRST -o $cmp -eq $COMPARE_ONLYSECOND ]; then 1041238423Sjhb panic "Changed file now missing" 1042238423Sjhb fi 1043238423Sjhb 1044238423Sjhb compare $NEWTREE/$file $DESTDIR/$file 1045238423Sjhb newdestcmp=$? 1046238423Sjhb if [ $newdestcmp -eq $COMPARE_EQUAL ]; then 1047238423Sjhb return 1048238423Sjhb fi 1049238423Sjhb 1050238423Sjhb # If the only change in the new file versus the destination 1051238423Sjhb # file is a change in the FreeBSD ID string and -F is 1052238423Sjhb # specified, just install the new file. 1053238423Sjhb if [ -n "$FREEBSD_ID" -a $newdestcmp -eq $COMPARE_DIFFFILES ] && \ 1054238423Sjhb fbsdid_only $NEWTREE/$file $DESTDIR/$file; then 1055238423Sjhb if update_unmodified $file; then 1056238423Sjhb return 1057238423Sjhb else 1058238423Sjhb panic "Updating FreeBSD ID string failed" 1059238423Sjhb fi 1060238423Sjhb fi 1061238423Sjhb 1062238423Sjhb # If the local file is the same as the old file, install the 1063238423Sjhb # new file. If -F is specified and the only local change is 1064238423Sjhb # in the FreeBSD ID string, then install the new file as well. 1065238423Sjhb if compare_fbsdid $OLDTREE/$file $DESTDIR/$file; then 1066238423Sjhb if update_unmodified $file; then 1067238423Sjhb return 1068238423Sjhb fi 1069238423Sjhb fi 1070238423Sjhb 1071238423Sjhb # If the file was removed from the dest tree, just whine. 1072238423Sjhb if [ $newdestcmp -eq $COMPARE_ONLYFIRST ]; then 1073238423Sjhb # If the removed file matches an ALWAYS_INSTALL glob, 1074238423Sjhb # then just install the new version of the file. 1075238423Sjhb if always_install $file; then 1076238423Sjhb log "ALWAYS: adding $file" 1077238423Sjhb if ! [ -d $NEWTREE/$file ]; then 1078238423Sjhb if install_new $file; then 1079238423Sjhb echo " A $file" 1080238423Sjhb fi 1081238423Sjhb fi 1082238423Sjhb return 1083238423Sjhb fi 1084238423Sjhb 1085258185Sjhb # If the only change in the new file versus the old 1086258185Sjhb # file is a change in the FreeBSD ID string and -F is 1087258185Sjhb # specified, don't warn. 1088258185Sjhb if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ 1089258185Sjhb fbsdid_only $OLDTREE/$file $NEWTREE/$file; then 1090258185Sjhb return 1091258185Sjhb fi 1092258185Sjhb 1093238423Sjhb case $cmp in 1094238423Sjhb $COMPARE_DIFFTYPE) 1095238423Sjhb old=`file_type $OLDTREE/$file` 1096238423Sjhb new=`file_type $NEWTREE/$file` 1097238423Sjhb warn "Remove mismatch: $file ($old became $new)" 1098238423Sjhb ;; 1099238423Sjhb $COMPARE_DIFFLINKS) 1100238423Sjhb old=`readlink $OLDTREE/$file` 1101238423Sjhb new=`readlink $NEWTREE/$file` 1102238423Sjhb warn \ 1103238423Sjhb "Removed link changed: $file (\"$old\" became \"$new\")" 1104238423Sjhb ;; 1105238423Sjhb $COMPARE_DIFFFILES) 1106238423Sjhb warn "Removed file changed: $file" 1107238423Sjhb ;; 1108238423Sjhb esac 1109238423Sjhb return 1110238423Sjhb fi 1111238423Sjhb 1112238423Sjhb # Treat the file as unmodified and force install of the new 1113238423Sjhb # file if it matches an ALWAYS_INSTALL glob. If the update 1114238423Sjhb # attempt fails, then fall through to the normal case so a 1115238423Sjhb # warning is generated. 1116238423Sjhb if always_install $file; then 1117238423Sjhb log "ALWAYS: updating $file" 1118238423Sjhb if update_unmodified $file; then 1119238423Sjhb return 1120238423Sjhb fi 1121238423Sjhb fi 1122238423Sjhb 1123258185Sjhb # If the only change in the new file versus the old file is a 1124258185Sjhb # change in the FreeBSD ID string and -F is specified, just 1125258185Sjhb # update the FreeBSD ID string in the local file. 1126258185Sjhb if [ -n "$FREEBSD_ID" -a $cmp -eq $COMPARE_DIFFFILES ] && \ 1127258185Sjhb fbsdid_only $OLDTREE/$file $NEWTREE/$file; then 1128258185Sjhb if update_freebsdid $file; then 1129258185Sjhb continue 1130258185Sjhb fi 1131258185Sjhb fi 1132258185Sjhb 1133238423Sjhb # If the file changed types between the old and new trees but 1134238423Sjhb # the files in the new and dest tree are both of the same 1135238423Sjhb # type, treat it like an added file just comparing the new and 1136238423Sjhb # dest files. 1137238423Sjhb if [ $cmp -eq $COMPARE_DIFFTYPE ]; then 1138238423Sjhb case $newdestcmp in 1139238423Sjhb $COMPARE_DIFFLINKS) 1140238423Sjhb new=`readlink $NEWTREE/$file` 1141238423Sjhb dest=`readlink $DESTDIR/$file` 1142238423Sjhb warn \ 1143238423Sjhb "New link conflict: $file (\"$new\" vs \"$dest\")" 1144238423Sjhb return 1145238423Sjhb ;; 1146238423Sjhb $COMPARE_DIFFFILES) 1147238423Sjhb new_conflict $file 1148238423Sjhb echo " C $file" 1149238423Sjhb return 1150238423Sjhb ;; 1151238423Sjhb esac 1152238423Sjhb else 1153238423Sjhb # If the file has not changed types between the old 1154238423Sjhb # and new trees, but it is a different type in 1155238423Sjhb # DESTDIR, then just warn. 1156238423Sjhb if [ $newdestcmp -eq $COMPARE_DIFFTYPE ]; then 1157238423Sjhb new=`file_type $NEWTREE/$file` 1158238423Sjhb dest=`file_type $DESTDIR/$file` 1159238423Sjhb warn "Modified mismatch: $file ($new vs $dest)" 1160238423Sjhb return 1161238423Sjhb fi 1162238423Sjhb fi 1163238423Sjhb 1164238423Sjhb case $cmp in 1165238423Sjhb $COMPARE_DIFFTYPE) 1166238423Sjhb old=`file_type $OLDTREE/$file` 1167238423Sjhb new=`file_type $NEWTREE/$file` 1168238423Sjhb dest=`file_type $DESTDIR/$file` 1169238423Sjhb warn "Modified $dest changed: $file ($old became $new)" 1170238423Sjhb ;; 1171238423Sjhb $COMPARE_DIFFLINKS) 1172238423Sjhb old=`readlink $OLDTREE/$file` 1173238423Sjhb new=`readlink $NEWTREE/$file` 1174238423Sjhb warn \ 1175238423Sjhb "Modified link changed: $file (\"$old\" became \"$new\")" 1176238423Sjhb ;; 1177238423Sjhb $COMPARE_DIFFFILES) 1178238423Sjhb merge_file $file 1179238423Sjhb ;; 1180238423Sjhb esac 1181238423Sjhb} 1182238423Sjhb 1183238423Sjhb# Handle a file that has been added in the new tree. If the file does 1184238423Sjhb# not exist in DESTDIR, simply copy the file into DESTDIR. If the 1185238423Sjhb# file exists in the DESTDIR and is identical to the new version, do 1186238423Sjhb# nothing. Otherwise, generate a diff of the two versions of the file 1187238423Sjhb# and mark it as a conflict. 1188238423Sjhb# 1189238423Sjhb# $1 - pathname of the file (relative to DESTDIR) 1190238423Sjhbhandle_added_file() 1191238423Sjhb{ 1192238423Sjhb local cmp dest file new 1193238423Sjhb 1194238423Sjhb file=$1 1195238423Sjhb if ignore $file; then 1196238423Sjhb log "IGNORE: added file $file" 1197238423Sjhb return 1198238423Sjhb fi 1199238423Sjhb 1200238423Sjhb compare $DESTDIR/$file $NEWTREE/$file 1201238423Sjhb cmp=$? 1202238423Sjhb case $cmp in 1203238423Sjhb $COMPARE_EQUAL) 1204238423Sjhb return 1205238423Sjhb ;; 1206238423Sjhb $COMPARE_ONLYFIRST) 1207238423Sjhb panic "Added file now missing" 1208238423Sjhb ;; 1209238423Sjhb $COMPARE_ONLYSECOND) 1210238423Sjhb # Ignore new directories. They will be 1211238423Sjhb # created as needed when non-directory nodes 1212238423Sjhb # are installed. 1213238423Sjhb if ! [ -d $NEWTREE/$file ]; then 1214238423Sjhb if install_new $file; then 1215238423Sjhb echo " A $file" 1216238423Sjhb fi 1217238423Sjhb fi 1218238423Sjhb return 1219238423Sjhb ;; 1220238423Sjhb esac 1221238423Sjhb 1222238423Sjhb 1223238423Sjhb # Treat the file as unmodified and force install of the new 1224238423Sjhb # file if it matches an ALWAYS_INSTALL glob. If the update 1225238423Sjhb # attempt fails, then fall through to the normal case so a 1226238423Sjhb # warning is generated. 1227238423Sjhb if always_install $file; then 1228238423Sjhb log "ALWAYS: updating $file" 1229238423Sjhb if update_unmodified $file; then 1230238423Sjhb return 1231238423Sjhb fi 1232238423Sjhb fi 1233238423Sjhb 1234238423Sjhb case $cmp in 1235238423Sjhb $COMPARE_DIFFTYPE) 1236238423Sjhb new=`file_type $NEWTREE/$file` 1237238423Sjhb dest=`file_type $DESTDIR/$file` 1238238423Sjhb warn "New file mismatch: $file ($new vs $dest)" 1239238423Sjhb ;; 1240238423Sjhb $COMPARE_DIFFLINKS) 1241238423Sjhb new=`readlink $NEWTREE/$file` 1242238423Sjhb dest=`readlink $DESTDIR/$file` 1243238423Sjhb warn "New link conflict: $file (\"$new\" vs \"$dest\")" 1244238423Sjhb ;; 1245238423Sjhb $COMPARE_DIFFFILES) 1246238423Sjhb # If the only change in the new file versus 1247238423Sjhb # the destination file is a change in the 1248238423Sjhb # FreeBSD ID string and -F is specified, just 1249238423Sjhb # install the new file. 1250238423Sjhb if [ -n "$FREEBSD_ID" ] && \ 1251238423Sjhb fbsdid_only $NEWTREE/$file $DESTDIR/$file; then 1252238423Sjhb if update_unmodified $file; then 1253238423Sjhb return 1254238423Sjhb else 1255238423Sjhb panic \ 1256238423Sjhb "Updating FreeBSD ID string failed" 1257238423Sjhb fi 1258238423Sjhb fi 1259238423Sjhb 1260238423Sjhb new_conflict $file 1261238423Sjhb echo " C $file" 1262238423Sjhb ;; 1263238423Sjhb esac 1264238423Sjhb} 1265238423Sjhb 1266238423Sjhb# Main routines for each command 1267238423Sjhb 1268238423Sjhb# Build a new tree and save it in a tarball. 1269238423Sjhbbuild_cmd() 1270238423Sjhb{ 1271238423Sjhb local dir 1272238423Sjhb 1273238423Sjhb if [ $# -ne 1 ]; then 1274238423Sjhb echo "Missing required tarball." 1275238423Sjhb echo 1276238423Sjhb usage 1277238423Sjhb fi 1278238423Sjhb 1279238423Sjhb log "build command: $1" 1280238423Sjhb 1281238423Sjhb # Create a temporary directory to hold the tree 1282238423Sjhb dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` 1283238423Sjhb if [ $? -ne 0 ]; then 1284238423Sjhb echo "Unable to create temporary directory." 1285238423Sjhb exit 1 1286238423Sjhb fi 1287238423Sjhb if ! build_tree $dir; then 1288238423Sjhb echo "Failed to build tree." 1289238423Sjhb remove_tree $dir 1290238423Sjhb exit 1 1291238423Sjhb fi 1292238423Sjhb if ! tar cfj $1 -C $dir . >&3 2>&1; then 1293238423Sjhb echo "Failed to create tarball." 1294238423Sjhb remove_tree $dir 1295238423Sjhb exit 1 1296238423Sjhb fi 1297238423Sjhb remove_tree $dir 1298238423Sjhb} 1299238423Sjhb 1300238423Sjhb# Output a diff comparing the tree at DESTDIR to the current 1301238423Sjhb# unmodified tree. Note that this diff does not include files that 1302238423Sjhb# are present in DESTDIR but not in the unmodified tree. 1303238423Sjhbdiff_cmd() 1304238423Sjhb{ 1305238423Sjhb local file 1306238423Sjhb 1307238423Sjhb if [ $# -ne 0 ]; then 1308238423Sjhb usage 1309238423Sjhb fi 1310238423Sjhb 1311238423Sjhb # Requires an unmodified tree to diff against. 1312238423Sjhb if ! [ -d $NEWTREE ]; then 1313238423Sjhb echo "Reference tree to diff against unavailable." 1314238423Sjhb exit 1 1315238423Sjhb fi 1316238423Sjhb 1317238423Sjhb # Unfortunately, diff alone does not quite provide the right 1318238423Sjhb # level of options that we want, so improvise. 1319238423Sjhb for file in `(cd $NEWTREE; find .) | sed -e 's/^\.//'`; do 1320238423Sjhb if ignore $file; then 1321238423Sjhb continue 1322238423Sjhb fi 1323238423Sjhb 1324238423Sjhb diffnode $NEWTREE "$DESTDIR" $file "stock" "local" 1325238423Sjhb done 1326238423Sjhb} 1327238423Sjhb 1328238423Sjhb# Just extract a new tree into NEWTREE either by building a tree or 1329238423Sjhb# extracting a tarball. This can be used to bootstrap updates by 1330238423Sjhb# initializing the current "stock" tree to match the currently 1331238423Sjhb# installed system. 1332238423Sjhb# 1333238423Sjhb# Unlike 'update', this command does not rotate or preserve an 1334238423Sjhb# existing NEWTREE, it just replaces any existing tree. 1335238423Sjhbextract_cmd() 1336238423Sjhb{ 1337238423Sjhb 1338238423Sjhb if [ $# -ne 0 ]; then 1339238423Sjhb usage 1340238423Sjhb fi 1341238423Sjhb 1342238423Sjhb log "extract command: tarball=$tarball" 1343238423Sjhb 1344238423Sjhb if [ -d $NEWTREE ]; then 1345238423Sjhb if ! remove_tree $NEWTREE; then 1346238423Sjhb echo "Unable to remove current tree." 1347238423Sjhb exit 1 1348238423Sjhb fi 1349238423Sjhb fi 1350238423Sjhb 1351238423Sjhb extract_tree 1352238423Sjhb} 1353238423Sjhb 1354238423Sjhb# Resolve conflicts left from an earlier merge. 1355238423Sjhbresolve_cmd() 1356238423Sjhb{ 1357238423Sjhb local conflicts 1358238423Sjhb 1359238423Sjhb if [ $# -ne 0 ]; then 1360238423Sjhb usage 1361238423Sjhb fi 1362238423Sjhb 1363238423Sjhb if ! [ -d $CONFLICTS ]; then 1364238423Sjhb return 1365238423Sjhb fi 1366238423Sjhb 1367258066Sjhb if ! [ -d $NEWTREE ]; then 1368258066Sjhb echo "The current tree is not present to resolve conflicts." 1369258066Sjhb exit 1 1370258066Sjhb fi 1371258066Sjhb 1372238423Sjhb conflicts=`(cd $CONFLICTS; find . ! -type d) | sed -e 's/^\.//'` 1373238423Sjhb for file in $conflicts; do 1374238423Sjhb resolve_conflict $file 1375238423Sjhb done 1376238423Sjhb 1377238423Sjhb if [ -n "$NEWALIAS_WARN" ]; then 1378238423Sjhb warn "Needs update: /etc/mail/aliases.db" \ 1379238423Sjhb "(requires manual update via newaliases(1))" 1380238423Sjhb echo 1381238423Sjhb echo "Warnings:" 1382238423Sjhb echo " Needs update: /etc/mail/aliases.db" \ 1383238423Sjhb "(requires manual update via newaliases(1))" 1384238423Sjhb fi 1385238423Sjhb} 1386238423Sjhb 1387238423Sjhb# Report a summary of the previous merge. Specifically, list any 1388238423Sjhb# remaining conflicts followed by any warnings from the previous 1389238423Sjhb# update. 1390238423Sjhbstatus_cmd() 1391238423Sjhb{ 1392238423Sjhb 1393238423Sjhb if [ $# -ne 0 ]; then 1394238423Sjhb usage 1395238423Sjhb fi 1396238423Sjhb 1397238423Sjhb if [ -d $CONFLICTS ]; then 1398238423Sjhb (cd $CONFLICTS; find . ! -type d) | sed -e 's/^\./ C /' 1399238423Sjhb fi 1400238423Sjhb if [ -s $WARNINGS ]; then 1401238423Sjhb echo "Warnings:" 1402238423Sjhb cat $WARNINGS 1403238423Sjhb fi 1404238423Sjhb} 1405238423Sjhb 1406238423Sjhb# Perform an actual merge. The new tree can either already exist (if 1407238423Sjhb# rerunning a merge), be extracted from a tarball, or generated from a 1408238423Sjhb# source tree. 1409238423Sjhbupdate_cmd() 1410238423Sjhb{ 1411238423Sjhb local dir 1412238423Sjhb 1413238423Sjhb if [ $# -ne 0 ]; then 1414238423Sjhb usage 1415238423Sjhb fi 1416238423Sjhb 1417258066Sjhb log "update command: rerun=$rerun tarball=$tarball preworld=$preworld" 1418238423Sjhb 1419238423Sjhb if [ `id -u` -ne 0 ]; then 1420238423Sjhb echo "Must be root to update a tree." 1421238423Sjhb exit 1 1422238423Sjhb fi 1423238423Sjhb 1424238423Sjhb # Enforce a sane umask 1425238423Sjhb umask 022 1426238423Sjhb 1427238423Sjhb # XXX: Should existing conflicts be ignored and removed during 1428238423Sjhb # a rerun? 1429238423Sjhb 1430238423Sjhb # Trim the conflicts tree. Whine if there is anything left. 1431238423Sjhb if [ -e $CONFLICTS ]; then 1432238423Sjhb find -d $CONFLICTS -type d -empty -delete >&3 2>&1 1433238423Sjhb rmdir $CONFLICTS >&3 2>&1 1434238423Sjhb fi 1435238423Sjhb if [ -d $CONFLICTS ]; then 1436238423Sjhb echo "Conflicts remain from previous update, aborting." 1437238423Sjhb exit 1 1438238423Sjhb fi 1439238423Sjhb 1440238423Sjhb if [ -z "$rerun" ]; then 1441238423Sjhb # For a dryrun that is not a rerun, do not rotate the existing 1442238423Sjhb # stock tree. Instead, extract a tree to a temporary directory 1443238423Sjhb # and use that for the comparison. 1444238423Sjhb if [ -n "$dryrun" ]; then 1445238423Sjhb dir=`mktemp -d $WORKDIR/etcupdate-XXXXXXX` 1446238423Sjhb if [ $? -ne 0 ]; then 1447238423Sjhb echo "Unable to create temporary directory." 1448238423Sjhb exit 1 1449238423Sjhb fi 1450258066Sjhb 1451258066Sjhb # A pre-world dryrun has already set OLDTREE to 1452258066Sjhb # point to the current stock tree. 1453258066Sjhb if [ -z "$preworld" ]; then 1454258066Sjhb OLDTREE=$NEWTREE 1455258066Sjhb fi 1456238423Sjhb NEWTREE=$dir 1457238423Sjhb 1458258066Sjhb # For a pre-world update, blow away any pre-existing 1459258066Sjhb # NEWTREE. 1460258066Sjhb elif [ -n "$preworld" ]; then 1461258066Sjhb if ! remove_tree $NEWTREE; then 1462258066Sjhb echo "Unable to remove pre-world tree." 1463258066Sjhb exit 1 1464258066Sjhb fi 1465258066Sjhb 1466238423Sjhb # Rotate the existing stock tree to the old tree. 1467238423Sjhb elif [ -d $NEWTREE ]; then 1468238423Sjhb # First, delete the previous old tree if it exists. 1469238423Sjhb if ! remove_tree $OLDTREE; then 1470238423Sjhb echo "Unable to remove old tree." 1471238423Sjhb exit 1 1472238423Sjhb fi 1473238423Sjhb 1474238423Sjhb # Move the current stock tree. 1475238423Sjhb if ! mv $NEWTREE $OLDTREE >&3 2>&1; then 1476238423Sjhb echo "Unable to rename current stock tree." 1477238423Sjhb exit 1 1478238423Sjhb fi 1479238423Sjhb fi 1480238423Sjhb 1481238423Sjhb if ! [ -d $OLDTREE ]; then 1482238423Sjhb cat <<EOF 1483238423SjhbNo previous tree to compare against, a sane comparison is not possible. 1484238423SjhbEOF 1485238423Sjhb log "No previous tree to compare against." 1486238423Sjhb if [ -n "$dir" ]; then 1487238423Sjhb rmdir $dir 1488238423Sjhb fi 1489238423Sjhb exit 1 1490238423Sjhb fi 1491238423Sjhb 1492238423Sjhb # Populate the new tree. 1493238423Sjhb extract_tree 1494238423Sjhb fi 1495238423Sjhb 1496238423Sjhb # Build lists of nodes in the old and new trees. 1497238423Sjhb (cd $OLDTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/old.files 1498238423Sjhb (cd $NEWTREE; find .) | sed -e 's/^\.//' | sort > $WORKDIR/new.files 1499238423Sjhb 1500238423Sjhb # Split the files up into three groups using comm. 1501238423Sjhb comm -23 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/removed.files 1502238423Sjhb comm -13 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/added.files 1503238423Sjhb comm -12 $WORKDIR/old.files $WORKDIR/new.files > $WORKDIR/both.files 1504238423Sjhb 1505238423Sjhb # Initialize conflicts and warnings handling. 1506238423Sjhb rm -f $WARNINGS 1507238423Sjhb mkdir -p $CONFLICTS 1508258066Sjhb 1509258066Sjhb # Ignore removed files for the pre-world case. A pre-world 1510258066Sjhb # update uses a stripped-down tree. 1511258066Sjhb if [ -n "$preworld" ]; then 1512258066Sjhb > $WORKDIR/removed.files 1513258066Sjhb fi 1514238423Sjhb 1515238423Sjhb # The order for the following sections is important. In the 1516238423Sjhb # odd case that a directory is converted into a file, the 1517238423Sjhb # existing subfiles need to be removed if possible before the 1518238423Sjhb # file is converted. Similarly, in the case that a file is 1519238423Sjhb # converted into a directory, the file needs to be converted 1520238423Sjhb # into a directory if possible before the new files are added. 1521238423Sjhb 1522238423Sjhb # First, handle removed files. 1523238423Sjhb for file in `cat $WORKDIR/removed.files`; do 1524238423Sjhb handle_removed_file $file 1525238423Sjhb done 1526238423Sjhb 1527238423Sjhb # For the directory pass, reverse sort the list to effect a 1528238423Sjhb # depth-first traversal. This is needed to ensure that if a 1529238423Sjhb # directory with subdirectories is removed, the entire 1530238423Sjhb # directory is removed if there are no local modifications. 1531238423Sjhb for file in `sort -r $WORKDIR/removed.files`; do 1532238423Sjhb handle_removed_directory $file 1533238423Sjhb done 1534238423Sjhb 1535238423Sjhb # Second, handle files that exist in both the old and new 1536238423Sjhb # trees. 1537238423Sjhb for file in `cat $WORKDIR/both.files`; do 1538238423Sjhb handle_modified_file $file 1539238423Sjhb done 1540238423Sjhb 1541238423Sjhb # Finally, handle newly added files. 1542238423Sjhb for file in `cat $WORKDIR/added.files`; do 1543238423Sjhb handle_added_file $file 1544238423Sjhb done 1545238423Sjhb 1546238423Sjhb if [ -n "$NEWALIAS_WARN" ]; then 1547238423Sjhb warn "Needs update: /etc/mail/aliases.db" \ 1548238423Sjhb "(requires manual update via newaliases(1))" 1549238423Sjhb fi 1550238423Sjhb 1551259134Sjhb # Run any special one-off commands after an update has completed. 1552259134Sjhb post_update 1553259134Sjhb 1554238423Sjhb if [ -s $WARNINGS ]; then 1555238423Sjhb echo "Warnings:" 1556238423Sjhb cat $WARNINGS 1557238423Sjhb fi 1558238423Sjhb 1559238423Sjhb if [ -n "$dir" ]; then 1560238423Sjhb if [ -z "$dryrun" -o -n "$rerun" ]; then 1561238423Sjhb panic "Should not have a temporary directory" 1562238423Sjhb fi 1563238423Sjhb 1564238423Sjhb remove_tree $dir 1565238423Sjhb fi 1566238423Sjhb} 1567238423Sjhb 1568238423Sjhb# Determine which command we are executing. A command may be 1569238423Sjhb# specified as the first word. If one is not specified then 'update' 1570238423Sjhb# is assumed as the default command. 1571238423Sjhbcommand="update" 1572238423Sjhbif [ $# -gt 0 ]; then 1573238423Sjhb case "$1" in 1574238423Sjhb build|diff|extract|status|resolve) 1575238423Sjhb command="$1" 1576238423Sjhb shift 1577238423Sjhb ;; 1578238423Sjhb -*) 1579238423Sjhb # If first arg is an option, assume the 1580238423Sjhb # default command. 1581238423Sjhb ;; 1582238423Sjhb *) 1583238423Sjhb usage 1584238423Sjhb ;; 1585238423Sjhb esac 1586238423Sjhbfi 1587238423Sjhb 1588238423Sjhb# Set default variable values. 1589238423Sjhb 1590238423Sjhb# The path to the source tree used to build trees. 1591238423SjhbSRCDIR=/usr/src 1592238423Sjhb 1593238423Sjhb# The destination directory where the modified files live. 1594238423SjhbDESTDIR= 1595238423Sjhb 1596238423Sjhb# Ignore changes in the FreeBSD ID string. 1597238423SjhbFREEBSD_ID= 1598238423Sjhb 1599238423Sjhb# Files that should always have the new version of the file installed. 1600238423SjhbALWAYS_INSTALL= 1601238423Sjhb 1602238423Sjhb# Files to ignore and never update during a merge. 1603238423SjhbIGNORE_FILES= 1604238423Sjhb 1605238423Sjhb# Flags to pass to 'make' when building a tree. 1606238423SjhbMAKE_OPTIONS= 1607238423Sjhb 1608238423Sjhb# Include a config file if it exists. Note that command line options 1609238423Sjhb# override any settings in the config file. More details are in the 1610238423Sjhb# manual, but in general the following variables can be set: 1611238423Sjhb# - ALWAYS_INSTALL 1612238423Sjhb# - DESTDIR 1613238423Sjhb# - EDITOR 1614238423Sjhb# - FREEBSD_ID 1615238423Sjhb# - IGNORE_FILES 1616238423Sjhb# - LOGFILE 1617238423Sjhb# - MAKE_OPTIONS 1618238423Sjhb# - SRCDIR 1619238423Sjhb# - WORKDIR 1620238423Sjhbif [ -r /etc/etcupdate.conf ]; then 1621238423Sjhb . /etc/etcupdate.conf 1622238423Sjhbfi 1623238423Sjhb 1624238423Sjhb# Parse command line options 1625238423Sjhbtarball= 1626238423Sjhbrerun= 1627238423Sjhbalways= 1628238423Sjhbdryrun= 1629238423Sjhbignore= 1630238423Sjhbnobuild= 1631258066Sjhbpreworld= 1632258066Sjhbwhile getopts "d:nprs:t:A:BD:FI:L:M:" option; do 1633238423Sjhb case "$option" in 1634238423Sjhb d) 1635238423Sjhb WORKDIR=$OPTARG 1636238423Sjhb ;; 1637238423Sjhb n) 1638238423Sjhb dryrun=YES 1639238423Sjhb ;; 1640258066Sjhb p) 1641258066Sjhb preworld=YES 1642258066Sjhb ;; 1643238423Sjhb r) 1644238423Sjhb rerun=YES 1645238423Sjhb ;; 1646238423Sjhb s) 1647238423Sjhb SRCDIR=$OPTARG 1648238423Sjhb ;; 1649238423Sjhb t) 1650238423Sjhb tarball=$OPTARG 1651238423Sjhb ;; 1652238423Sjhb A) 1653238423Sjhb # To allow this option to be specified 1654238423Sjhb # multiple times, accumulate command-line 1655238423Sjhb # specified patterns in an 'always' variable 1656238423Sjhb # and use that to overwrite ALWAYS_INSTALL 1657238423Sjhb # after parsing all options. Need to be 1658238423Sjhb # careful here with globbing expansion. 1659238423Sjhb set -o noglob 1660238423Sjhb always="$always $OPTARG" 1661238423Sjhb set +o noglob 1662238423Sjhb ;; 1663238423Sjhb B) 1664238423Sjhb nobuild=YES 1665238423Sjhb ;; 1666238423Sjhb D) 1667238423Sjhb DESTDIR=$OPTARG 1668238423Sjhb ;; 1669238423Sjhb F) 1670238423Sjhb FREEBSD_ID=YES 1671238423Sjhb ;; 1672238423Sjhb I) 1673238423Sjhb # To allow this option to be specified 1674238423Sjhb # multiple times, accumulate command-line 1675238423Sjhb # specified patterns in an 'ignore' variable 1676238423Sjhb # and use that to overwrite IGNORE_FILES after 1677238423Sjhb # parsing all options. Need to be careful 1678238423Sjhb # here with globbing expansion. 1679238423Sjhb set -o noglob 1680238423Sjhb ignore="$ignore $OPTARG" 1681238423Sjhb set +o noglob 1682238423Sjhb ;; 1683238423Sjhb L) 1684238423Sjhb LOGFILE=$OPTARG 1685238423Sjhb ;; 1686238423Sjhb M) 1687238423Sjhb MAKE_OPTIONS="$OPTARG" 1688238423Sjhb ;; 1689238423Sjhb *) 1690238423Sjhb echo 1691238423Sjhb usage 1692238423Sjhb ;; 1693238423Sjhb esac 1694238423Sjhbdone 1695238423Sjhbshift $((OPTIND - 1)) 1696238423Sjhb 1697238423Sjhb# Allow -A command line options to override ALWAYS_INSTALL set from 1698238423Sjhb# the config file. 1699238423Sjhbset -o noglob 1700238423Sjhbif [ -n "$always" ]; then 1701238423Sjhb ALWAYS_INSTALL="$always" 1702238423Sjhbfi 1703238423Sjhb 1704238423Sjhb# Allow -I command line options to override IGNORE_FILES set from the 1705238423Sjhb# config file. 1706238423Sjhbif [ -n "$ignore" ]; then 1707238423Sjhb IGNORE_FILES="$ignore" 1708238423Sjhbfi 1709238423Sjhbset +o noglob 1710238423Sjhb 1711238423Sjhb# Where the "old" and "new" trees are stored. 1712238423SjhbWORKDIR=${WORKDIR:-$DESTDIR/var/db/etcupdate} 1713238423Sjhb 1714238423Sjhb# Log file for verbose output from program that are run. The log file 1715238423Sjhb# is opened on fd '3'. 1716238423SjhbLOGFILE=${LOGFILE:-$WORKDIR/log} 1717238423Sjhb 1718238423Sjhb# The path of the "old" tree 1719238423SjhbOLDTREE=$WORKDIR/old 1720238423Sjhb 1721238423Sjhb# The path of the "new" tree 1722238423SjhbNEWTREE=$WORKDIR/current 1723238423Sjhb 1724238423Sjhb# The path of the "conflicts" tree where files with merge conflicts are saved. 1725238423SjhbCONFLICTS=$WORKDIR/conflicts 1726238423Sjhb 1727238423Sjhb# The path of the "warnings" file that accumulates warning notes from an update. 1728238423SjhbWARNINGS=$WORKDIR/warnings 1729238423Sjhb 1730238423Sjhb# Use $EDITOR for resolving conflicts. If it is not set, default to vi. 1731238423SjhbEDITOR=${EDITOR:-/usr/bin/vi} 1732238423Sjhb 1733258066Sjhb# Files that need to be updated before installworld. 1734258066SjhbPREWORLD_FILES="etc/master.passwd etc/group" 1735258066Sjhb 1736238423Sjhb# Handle command-specific argument processing such as complaining 1737238423Sjhb# about unsupported options. Since the configuration file is always 1738238423Sjhb# included, do not complain about extra command line arguments that 1739238423Sjhb# may have been set via the config file rather than the command line. 1740238423Sjhbcase $command in 1741238423Sjhb update) 1742238423Sjhb if [ -n "$rerun" -a -n "$tarball" ]; then 1743238423Sjhb echo "Only one of -r or -t can be specified." 1744238423Sjhb echo 1745238423Sjhb usage 1746238423Sjhb fi 1747258066Sjhb if [ -n "$rerun" -a -n "$preworld" ]; then 1748258066Sjhb echo "Only one of -p or -r can be specified." 1749258066Sjhb echo 1750258066Sjhb usage 1751258066Sjhb fi 1752238423Sjhb ;; 1753258066Sjhb build|diff|status) 1754258097Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" -o \ 1755258066Sjhb -n "$preworld" ]; then 1756258066Sjhb usage 1757258066Sjhb fi 1758258066Sjhb ;; 1759258066Sjhb resolve) 1760238423Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$tarball" ]; then 1761238423Sjhb usage 1762238423Sjhb fi 1763238423Sjhb ;; 1764238423Sjhb extract) 1765258066Sjhb if [ -n "$dryrun" -o -n "$rerun" -o -n "$preworld" ]; then 1766238423Sjhb usage 1767238423Sjhb fi 1768238423Sjhb ;; 1769238423Sjhbesac 1770238423Sjhb 1771258066Sjhb# Pre-world mode uses a different set of trees. It leaves the current 1772258066Sjhb# tree as-is so it is still present for a full etcupdate run after the 1773258066Sjhb# world install is complete. Instead, it installs a few critical files 1774258066Sjhb# into a separate tree. 1775258066Sjhbif [ -n "$preworld" ]; then 1776258066Sjhb OLDTREE=$NEWTREE 1777258066Sjhb NEWTREE=$WORKDIR/preworld 1778258066Sjhbfi 1779258066Sjhb 1780238423Sjhb# Open the log file. Don't truncate it if doing a minor operation so 1781238423Sjhb# that a minor operation doesn't lose log info from a major operation. 1782238423Sjhbif ! mkdir -p $WORKDIR 2>/dev/null; then 1783238423Sjhb echo "Failed to create work directory $WORKDIR" 1784238423Sjhbfi 1785238423Sjhb 1786238423Sjhbcase $command in 1787238423Sjhb diff|resolve|status) 1788238423Sjhb exec 3>>$LOGFILE 1789238423Sjhb ;; 1790238423Sjhb *) 1791238423Sjhb exec 3>$LOGFILE 1792238423Sjhb ;; 1793238423Sjhbesac 1794238423Sjhb 1795238423Sjhb${command}_cmd "$@" 1796