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