#!/bin/sh
#
# $NetBSD: etcupdate,v 1.65 2022/01/17 08:47:03 lukem Exp $
#
# Copyright (c) 2001-2022 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Martti Kuparinen.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# This script helps you to update the configuration files in /etc
# after an operating system upgrade. Instead of running "make distribution"
# in /usr/src/etc (and losing your current configuration) you can easily
# see the modifications and either install the new version or merge the
# changes in to your current configuration files.
#
# This script was written by Martti Kuparinen <martti@NetBSD.org> and
# improved by several other NetBSD users.
#
# The idea for this script (including code fragments, variable names etc.)
# came from the FreeBSD mergemaster (by Douglas Barton).
#
PATH="/sbin:/usr/sbin:/bin:/usr/bin:${PATH}"

# Default settings
PROG="${0##*/}"
DESTDIR=""		# must not have a trailing slash
DESTDIR_BRE=""		# basic regex to match ${DESTDIR}
TEMPROOT="${TEMPROOT:=/tmp/temproot}"
PAGER="${PAGER:=/usr/bin/more}"
SWIDTH="$(stty size | awk '{w=$2}END{if(w==0){w=80}print w}')"
WIDTH="${WIDTH:="${SWIDTH}"}"
DIFF_COMMAND="diff -u"
VERBOSE=false
CONTINUE=false
SOURCEMODE=false	# true for "-s source_dir"
SRCDIR=			# directory for SOURCEMODE
BINARYMODE=false	# true for both BINARYDIRMODE and BINARYTGZMODE
BINARYDIRMODE=false	# true for "-s extracted_dir"
BINARYDIR=		# directory name for BINARYDIRMODE
BINARYTGZMODE=false	# true for "-s etc.tgz"
TGZLIST=		# quoted list list of files for BINARYTGZMODE
SRC_ARGLIST=		# quoted list of "-s" args
N_SRC_ARGS=0		# number of "-s" args
AUTOMATIC=false
LOCALSKIP=false
MACHINE="${MACHINE:="$(uname -m)"}"
export MACHINE
MACHINE_ARCH="${MACHINE_ARCH:="$(uname -p)"}"
export MACHINE_ARCH

# Settings for post-installation procedures
NEED_ANYTHING=false
NEED_MAKEDEV=false
NEED_MTREE=false
NEED_NEWALIASES=false
NEED_PWD_MKDB=false
NEED_SERVICES_MKDB=false


help()
{
	cat << EOF

Usage: ${PROG} [-alv] [-d DESTDIR] [-p PAGER] [-s SRC_ARG] [-t TEMPROOT] [-w WIDTH]
       ${PROG} ( -h | -? )

Options:

  -a           Automatically update unmodified files.
  -d DESTDIR   Destination directory to check. [/]
  -h           Display this help, and exit.
  -l           Automatically skip files with strictly local changes
               (this option has no effect on files lacking RCS Ids).
  -p PAGER     Which pager to use              [/usr/bin/more]
  -s SRC_ARG   Location of the source files.   [/usr/src]
               This may be any of the following:
                 -s SRC_DIR   A directory that contains a NetBSD
                              source tree.
                 -s TGZ_DIR   A directory in which one or both of
                              "etc.tgz" and "xetc.tgz" have been extracted.
                 -s TGZ_FILE  A distribution set file such as "etc.tgz" or
                              "xetc.tgz".  May be specified multiple times.
  -t TEMPROOT  Where to store temporary files. [/tmp/temproot]
  -w WIDTH     Screen width.                   [80]
  -v           Be more verbose.
  -?           Display this help, and exit.

EOF
}

usage()
{
	help 1>&2
	exit 1
}

verbose()
{
	# $* = message to display if in verbose mode

	${VERBOSE} && echo "${@}"
}

yesno()
{
	# $* = message to display

	echo -n "${@}? (y/[n]) "
	read ANSWER
	case "${ANSWER}" in
		y|Y)
			return 0
			;;
		*)
			return 1
			;;
	esac
}

# Quote args to make them safe in the shell.
# Usage: quotedlist="$(shell_quote args...)"
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
#    eval "set -- $quotedlist"
# or like this:
#    eval "\$command $quotedlist \$filename"
#
shell_quote()
{(
	local result=''
	local arg qarg
	LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
	for arg in "$@" ; do
		case "${arg}" in
		'')
			qarg="''"
			;;
		*[!-./a-zA-Z0-9]*)
			# Convert each embedded ' to '\'',
			# then insert ' at the beginning of the first line,
			# and append ' at the end of the last line.
			# Finally, elide unnecessary '' pairs at the
			# beginning and end of the result and as part of
			# '\'''\'' sequences that result from multiple
			# adjacent quotes in he input.
			qarg="$(printf "%s\n" "$arg" | \
			    ${SED:-sed} -e "s/'/'\\\\''/g" \
				-e "1s/^/'/" -e "\$s/\$/'/" \
				-e "1s/^''//" -e "\$s/''\$//" \
				-e "s/'''/'/g"
				)"
			;;
		*)
			# Arg is not the empty string, and does not contain
			# any unsafe characters.  Leave it unchanged for
			# readability.
			qarg="${arg}"
			;;
		esac
		result="${result}${result:+ }${qarg}"
	done
	printf "%s\n" "$result"
)}

# Convert arg $1 to a basic regular expression (as in sed)
# that will match the arg.  This works by inserting backslashes
# before characters that are special in basic regular expressions.
# It also inserts backslashes before the extra characters specified
# in $2 (which defaults to "/,").
# XXX: Does not handle embedded newlines.
# Usage: regex="$(bre_quote "${string}")"
bre_quote()
{
	local arg="$1"
	local extra="${2-/,}"
	printf "%s\n" "${arg}" | sed -e 's/[][^$.*\\'"${extra}"']/\\&/g'
}

install_dir()
{
	# $1 = target directory (relative to ${DESTDIR})

	NEED_ANYTHING=true
	if yesno "Create ${DESTDIR}${1}"; then
		verbose "Creating ${DESTDIR}${1}"
		mkdir -p "${DESTDIR}${1}" || exit 1
		NEED_MTREE=true
	fi
}

install_file()
{
	# $1 = target file (relative to ${DESTDIR})

	NEED_ANYTHING=true
	# Install the new file
	verbose "Installing ${DESTDIR}${1}"
	cp -p "${TEMPROOT}${1}" "${DESTDIR}${1}" && rm -f "${TEMPROOT}${1}"

	# Check if this was a special file
	case "${1}" in
	/dev/MAKEDEV)
		NEED_MAKEDEV=true
		;;
	/dev/MAKEDEV.local)
		NEED_MAKEDEV=true
		;;
	/etc/mail/aliases)
		NEED_NEWALIASES=true
		;;
	/etc/master.passwd)
		NEED_PWD_MKDB=true
		;;
	/etc/services)
		NEED_SERVICES_MKDB=true
		;;
	esac
}

install_checksum()
{
	# $1 = target file (relative to ${DESTDIR})

	${AUTOMATIC} || return

	NEED_ANYTHING=true
	D="$(dirname "${1}")"
	mkdir -p "${DESTDIR}/var/etcupdate/${D}"
	verbose "Saving MD5 checksum for ${DESTDIR}${1} to" \
	    "${DESTDIR}/var/etcupdate/${1}"
	# The sed part of the following pipeline changes things like
	# "MD5 (/path/to/dest/dir/etc/filename) = abc123" to
	# "MD5 (/etc/filename) = abc123".
	md5 "${DESTDIR}${1}" | sed -e "s,(${DESTDIR_BRE},(," \
	    > "${DESTDIR}/var/etcupdate/${1}"
}

# Initialise the DIFF_EXTRA_OPTIONS variable.
init_diff_extra_options()
{
	#
	# Start with a few options that are always available.
	#
	DIFF_EXTRA_OPTIONS=\
"  su  Show differences in unified format (\"diff -u\")
  sc  Show differences in context format (\"diff -c\")
  ss  Show differences side by side (\"sdiff -w${WIDTH}\")"
	#
	# wdiff is not part of the base system, but the
	# user might have installed it from pkgsrc.  It is
	# useful to show differences on a word by word basis
	# instead of line by line.  If it is executable
	# then offer to use it in the menu.
	#
	if (wdiff /dev/null /dev/null) >/dev/null 2>&1 ; then
		DIFF_EXTRA_OPTIONS="${DIFF_EXTRA_OPTIONS}
  sw  Show differences word by word (\"wdiff -n -l\")"
	fi
	#
	# End with an option to use a user-specified diff-like command.
	#
	DIFF_EXTRA_OPTIONS="${DIFF_EXTRA_OPTIONS}
  scommand Show differences using the specified diff-like command"
}

diff_and_merge_file()
{
	# $1 = target file (relative to ${DESTDIR})

	if cmp -s "${TEMPROOT}${1}" "${DESTDIR}${1}"; then
		verbose "===> ${1} (ok)"
		rm -f "${TEMPROOT}${1}"
		install_checksum "${1}"
		return
	fi

	if ${AUTOMATIC} && [ -f "${DESTDIR}/var/etcupdate/${1}" ]; then
		SUM1="$(md5 "${1}")"
		SUM2="$(cat "${DESTDIR}/var/etcupdate/${1}")"
		if [ "${SUM1}" = "${SUM2}" ]; then
			install_file "${1}"
			install_checksum "${1}"
			return
		fi
	fi

	if ${LOCALSKIP}; then
		ID1="$(ident -q "${TEMPROOT}${1}" | sed -n 2p)"
		ID1="${ID1:-0}"
		ID2="$(ident -q "${DESTDIR}${1}" | sed -n 2p)"
		ID2="${ID2:-1}"
		if [ "${ID1}" = "${ID2}" ]; then
			verbose "===> ${1} (ok:RCS)"
			rm -f "${TEMPROOT}${1}"
			return
		fi
	fi

	clear
	if [ ! -f "${DESTDIR}${1}" ]; then
		verbose "===> ${DESTDIR}${1} (missing)"
		DOES_EXIST=false
	else
		verbose "===> ${DESTDIR}${1} (modified)"
		verbose ""
		DOES_EXIST=true
		diff -u "${DESTDIR}${1}" "${TEMPROOT}${1}" | ${PAGER}
	fi

	STAY_HERE=true
	ALREADY_MERGED=false

	# Determine name for the backup file (/foo/._etcupdate.bar)
	D="$(dirname  "${TEMPROOT}${1}")"
	F="$(basename "${TEMPROOT}${1}")"
	B="${D}/.etcupdate.${F}"
	F="${D}/${F}"

	while ${STAY_HERE}; do

		# Ask the user if (s)he wants to install the new
		# version or perform a more complicated manual work.
		echo ""
		echo -n "File: ${DESTDIR}${1}"
		if [ ! -f "${DESTDIR}${1}" ]; then
			echo -n " (missing)"
		else
			echo -n " (modified)"
		fi
		echo ""
		echo ""
		echo "Please select one of the following operations:"
		echo ""
		if ! ${DOES_EXIST}; then
			cat << EOF
  d  Don't install the missing file
  i  Install the missing file
  v  Show the missing file

EOF
		elif ! ${ALREADY_MERGED}; then
			cat << EOF
  d  Don't install the new file (keep your old file)
  i  Install the new file (overwrites your local modifications!)
  m  Merge the currently installed and new files
  s  Show the differences between the currently installed and new files
${DIFF_EXTRA_OPTIONS}
  v  Show the new file

EOF
		else
			cat << EOF
  d  Don't install the merged file (keep your old file)
  i  Install the merged file (overwrites your old file)
  m  Merge again (your old file against the result from the previous merge)
  s  Show the differences between the currently installed and new merged files
${DIFF_EXTRA_OPTIONS}
  u  Undo merge (start again with the original version of the new file)
  v  Show the merged file

EOF
		fi
		echo -n "What do you want to do? [Leave it for later] "
		read ANSWER
		case "${ANSWER}" in

		[dD])
			verbose "Removing ${TEMPROOT}${1}"
			rm -f "${TEMPROOT}${1}"
			STAY_HERE=false
			;;
		[iI])
			install_file "${1}"
			if ! ${ALREADY_MERGED}; then
				install_checksum "${1}"
			fi
			STAY_HERE=false
			;;
		[mM])
			${DOES_EXIST} || continue
			[ ! -f "${B}" ] && cp "${F}" "${B}"
			cp "${TEMPROOT}${1}" "${TEMPROOT}${1}.merged"
			sdiff -o "${TEMPROOT}${1}.merged"	\
				--width=${WIDTH}		\
				--suppress-common-lines --text	\
				"${DESTDIR}${1}" "${TEMPROOT}${1}"
			mv -f "${TEMPROOT}${1}.merged" "${TEMPROOT}${1}"
			ALREADY_MERGED=true
			;;
		[sS]*)
			${DOES_EXIST} || continue
			case "${ANSWER}" in
			[sS])	: no change ;;
			[sS]u)	DIFF_COMMAND="diff -u" ;;
			[sS]c)	DIFF_COMMAND="diff -c" ;;
			[sS]s)	DIFF_COMMAND="sdiff -w${WIDTH}" ;;
			[sS]w)	DIFF_COMMAND="wdiff -n -l" ;;
			[sS]*)	DIFF_COMMAND="${ANSWER#?}" ;;
			esac
			${DIFF_COMMAND} "${DESTDIR}${1}" "${TEMPROOT}${1}" \
				| ${PAGER}
			;;
		[uU])
			if [ -f "${B}" ]; then
				echo "*** Restoring ${F}"
				mv -f "${B}" "${F}"
			fi
			ALREADY_MERGED=false
			;;
		[vV])
			${PAGER} "${TEMPROOT}${1}"
			;;
		"")
			STAY_HERE=false
			;;
		*)
			echo "*** Invalid selection!"
			;;
		esac
	done
	rm -f "$B"
}

# Set the environment for make.
set_makeenv()
{
	#
	# INSTALL_DONE=1 prevents installation of unwanted
	# files (things that are not part of the etc set).
	# BUILD=1 allows building of files that are wanted.
	#
	MAKE_ENV=" 			\
		NETBSDSRCDIR=$(shell_quote "${SRCDIR}")	\
		DESTDIR=$(shell_quote "${TEMPROOT}")	\
		MAKE=make		\
		MTREE=mtree		\
		TOOL_MTREE=mtree	\
		INSTALL_DONE=1		\
		BUILD=1			\
		USETOOLS=never"
}

#
# main()
#

# Read global configuration
GLOBALRC="/etc/${PROG}.conf"
[ -r ${GLOBALRC} ] && . ${GLOBALRC}

# Read user configuration
USERRC="${HOME}/.{PROG}rc"
[ -r ${USERRC} ] && . ${USERRC}

# Read command line arguments
while getopts :ad:hlp:s:t:vw: i
do
	case "${i}" in
	a)
		AUTOMATIC=true
		;;
	d)
		DESTDIR="${OPTARG}"
		;;
	h)
		help
		exit 0
		;;
	l)
		LOCALSKIP=true
		;;
	p)
		PAGER="${OPTARG}"
		;;
	s)
		# Three cases:
		# -s tgzfile       (may be repeated)
		# -s srcdir        (may not be repeated)
		# -s extracted_dir (may not be repeated)
		arg="${OPTARG}"
		qarg="$(shell_quote "${OPTARG}")"
		N_SRC_ARGS=$(( N_SRC_ARGS + 1 ))
		SRC_ARGLIST="${SRC_ARGLIST}${SRC_ARGLIST:+ }-s ${qarg}"
		if [ -f "${arg}" ]; then
			# arg refers to a *.tgz file.
			# This may happen twice, for both etc.tgz and
			# xetc.tgz, so we build up a list in TGZLIST.
			BINARYMODE=true
			BINARYTGZMODE=true
			TGZLIST="${TGZLIST}${TGZLIST:+ }${qarg}"
		elif [ -d "${arg}" ] && [ -f "${arg}/etc/Makefile" ]; then
			# arg refers to a source directory
			SOURCEMODE=true
			SRCDIR="${arg}"
		elif [ -d "${arg}" ] && [ -d "${arg}/etc" ] \
			&& ! [ -f "${arg}/etc/Makefile" ]
		then
			# arg refers to a directory where the
			# sets have already been extracted
			BINARYMODE=true
			BINARYDIRMODE=true
			BINARYDIR="${arg}"
		else
			echo "*** Nonexistent or invalid file or directory" \
			     "for -s ${arg}"
			usage
		fi
		;;
	t)
		TEMPROOT="${OPTARG}"
		;;
	v)
		VERBOSE=true
		;;
	w)
		WIDTH="${OPTARG}"
		;;
	"?")
		if [ "${OPTARG}" = "?" ]; then
			help
			exit 0
		fi
		echo 1>&2 "${PROG}: Unknown option -${OPTARG}"
		usage
		;;

	:)
		echo 1>&2 "${PROG}: Missing argument for option -${OPTARG}"
		usage
		;;

	*)
		echo 1>&2 "${PROG}: Unimplemented option -${i}"
		exit 3
		;;
	esac
done
shift $((${OPTIND} - 1))
if [ $# -ne 0 ] ; then
	echo 1>&2 "${PROG}: Unknown extra arguments"
	usage
fi

# Last minute sanity checks
if [ "$(id -u)" -ne 0 ]; then
	echo "*** ERROR: You MUST be root"
	exit 1
fi
DESTDIR="${DESTDIR%/}" # remove trailing slash, if any.  result might be "".
DESTDIR_BRE="$(bre_quote "${DESTDIR}")"
if [ "${N_SRC_ARGS}" -gt 1 ] && ( ${SOURCEMODE} || ${BINARYDIRMODE} ); then
	echo 1>&2 "${PROG}: Multiple -s args are allowed only with tgz files"
	usage
fi
case "${TEMPROOT}" in
/*) : OK ;;
*)  new="${PWD:-$(pwd)}/${TEMPROOT}"
    echo "*** NOTE: Using TEMPROOT \"${new}\" instead of \"${TEMPROOT}\""
    TEMPROOT="${new}"
    ;;
esac
if ${BINARYDIRMODE}; then
	SRCDIR="${TEMPROOT}"
fi
if ${BINARYTGZMODE}; then
	SRCDIR="${TEMPROOT}"
fi
if [ "${N_SRC_ARGS}" -eq 0 ]; then
	# default if no "-s" option was specified
	SOURCEMODE=true
	SRCDIR="/usr/src"
	SRC_ARGLIST="-s $(shell_quote "${SRCDIR}")"
fi
if [ -z "${SRCDIR}" -o -z "${TEMPROOT}" ]; then
	echo "*** ERROR: One of the following variables is undefined"
	echo ""
	echo "SRCDIR=\"${SRCDIR}\""
	echo "TEMPROOT=\"${TEMPROOT}\""
	echo ""
	exit 1
fi
if [ -r "${TEMPROOT}" ]; then
	echo ""
	echo "*** WARNING: ${TEMPROOT} already exists"
	echo ""
	if yesno "Continue previously aborted update"; then
		CONTINUE=true
	elif yesno "Remove the old ${TEMPROOT}"; then
		echo "*** Removing ${TEMPROOT}"
		rm -rf "${TEMPROOT}"
	fi
fi

if ! ${CONTINUE}; then
	# Create the temporary root directory
	echo "*** Creating ${TEMPROOT}"
	mkdir -p "${TEMPROOT}"
	if [ ! -d "${TEMPROOT}" ]; then
		echo "*** ERROR: Unable to create ${TEMPROOT}"
		exit 1
	fi
	# Are we using the sources or binaries?
	if ${BINARYTGZMODE}; then
		# Populate ${TEMPROOT} from ${TGZLIST}
		eval "set -- ${TGZLIST}"
		for tgz in "$@"; do
			if [ ! -f "${tgz}" ]; then
				echo "*** ERROR: Unable to find ${tgz}"
				exit 1
			fi
			echo "*** Populating ${TEMPROOT} from ${tgz}"
			tar -zxpf "${tgz}" -C "${TEMPROOT}"
			[ $? -ne 0 ] && exit 1
		done
	elif ${BINARYDIRMODE}; then
		# Populate ${TEMPROOT} from ${SRCDIR} by copying.
		# Copy only the files that belong to the etc and xetc sets.
		echo "*** Populating ${TEMPROOT} from ${BINARYDIR} (copying)"
		for setname in etc xetc; do
			mtreefile="${BINARYDIR}/etc/mtree/set.${setname}"
			if ${VERBOSE}; then vflag="-v"; else vflag=""; fi
			if [ -f "${mtreefile}" ]; then
				echo "*** Copying files belonging to" \
				     "${setname} set"
				(cd "${BINARYDIR}" \
				 && pax -rwdM ${vflag} "${TEMPROOT%/}/."
				) <"${mtreefile}"
				[ $? -ne 0 ] && exit 1
			else
				echo "*** Not copying files belonging to" \
				     "${setname} set: ${mtreefile} not found"
			fi
		done
	elif ${SOURCEMODE}; then
		# Populate ${TEMPROOT} from ${SRCDIR} by running make
		if [ ! -f "${SRCDIR}/etc/Makefile" ]; then
			echo "*** ERROR: Unable to find ${SRCDIR}/etc/Makefile"
			exit 1
		fi
		set_makeenv
		echo "*** Populating ${TEMPROOT} from ${SRCDIR} (make distribution)"
		cd ${SRCDIR}/etc
		if ! ${VERBOSE}; then
			eval "${MAKE_ENV} make distribution > /dev/null"
		else
			eval "${MAKE_ENV} make distribution"
		fi
		[ $? -ne 0 ] && exit 1
	fi
	if ! [ -f "${TEMPROOT}/etc/mtree/set.etc" ]; then
		echo "*** ERROR: Files from the etc.tgz set are missing"
		exit 1
	fi
	if [ ! -f "${TEMPROOT}/dev/MAKEDEV" ]; then
		echo ""
		echo "*** WARNING: ${TEMPROOT}/dev/MAKEDEV not found"
		echo "Make sure you update /dev/MAKEDEV later and run"
		echo "(cd /dev && ./MAKEDEV all) to rebuild the device nodes"
		echo ""
	fi

	# Ignore the following files during comparison
	rm -f "${TEMPROOT}"/etc/passwd
	rm -f "${TEMPROOT}"/etc/pwd.db
	rm -f "${TEMPROOT}"/etc/spwd.db
	find "${TEMPROOT}" -type f -size 0 -exec rm {} \;

	# Ignore files we're told to ignore
	if [ ! -z "${IGNOREFILES}" ]; then
		echo "*** Ignoring files: ${IGNOREFILES}"
		for file in ${IGNOREFILES}; do
			rm -f "${TEMPROOT}"${file}
		done
	fi

	# Are there any new directories?
	echo "*** Checking for new directories"
	exec 3<&0
	find "${TEMPROOT}" -type d | \
	while read i; do
		D="${i#"${TEMPROOT}"}"
		[ "x${i}" = "x${TEMPROOT}" ] && continue
		[ ! -d "${D}" ] && install_dir "${D}" <&3
	done
fi

# Start the comparison
echo "*** Checking for added/modified files"
init_diff_extra_options
exec 3<&0
find "${TEMPROOT}" -type f  -a ! -name \*.etcupdate.\* | \
while read i; do
	D="${i#"${TEMPROOT}"}"
	diff_and_merge_file "${D}" <&3
done

# Do we have files which were not processed?
REMAINING="$(find "${TEMPROOT}" -type f -a ! -name \*.etcupdate.\*)"
if [ ! -z "${REMAINING}" ]; then
	echo ""
	echo "*** The following files need your attention:"
	echo ""
	echo "${REMAINING}" | sed -e 's/^/  /'
	echo ""
elif ! ${NEED_ANYTHING}; then
	echo ""
	echo "*** No changes were needed"
	echo ""
fi
if yesno "Remove ${TEMPROOT}"; then
	echo "*** Removing ${TEMPROOT}"
	rm -rf "${TEMPROOT}"
else
	echo "*** Keeping ${TEMPROOT}"
fi

# Clean up after "make distribution"
if ${SOURCEMODE}; then
	echo "*** Cleaning up in ${SRCDIR}/etc"
	set_makeenv
	cd ${SRCDIR}/etc
	if ! ${VERBOSE}; then
		eval "${MAKE_ENV} make clean > /dev/null"
	else
		eval "${MAKE_ENV} make clean"
	fi
fi

# Do some post-installation tasks
if ${NEED_PWD_MKDB}; then
	pwd_mkdb_cmd="$(shell_quote \
	    pwd_mkdb ${DESTDIR:+-d "${DESTDIR}"} \
	    -p "${DESTDIR}/etc/master.passwd")"
	if yesno "Do you want to rebuild the password databases from the" \
	         "new ${DESTDIR}/etc/master.passwd"
	then
		verbose "Running pwd_mkdb"
		eval "${pwd_mkdb_cmd}"
	else
		echo ""
		echo "*** You MUST rebuild the password databases to make" \
		     "the changes visible"
		echo "*** This is done by running \"${pwd_mkdb_cmd}\" as root"
		echo ""
	fi
fi

if ! ${NEED_SERVICES_MKDB}; then
	if test -e "${DESTDIR}/var/db/services.db" \
	   -a ! -e "${DESTDIR}/var/db/services.cdb"
	then
		NEED_SERVICES_MKDB=true
	fi
fi

if ${NEED_SERVICES_MKDB}; then
	services_mkdb_cmd="$(shell_quote services_mkdb -V cdb \
	    -o "${DESTDIR}/var/db/services.cdb" \
	    "${DESTDIR}/etc/services")"
	if yesno "Do you want to rebuild the services databases from the" \
	         "new ${DESTDIR}/etc/services"
	then
		verbose "Running services_mkdb"
		eval "${services_mkdb_cmd}"
	else
		echo ""
		echo "*** You SHOULD rebuild the services databases to make" \
		     "the changes visible"
		echo "*** This is done by running \"${services_mkdb_cmd}\"" \
		     "as root"
		echo ""
	fi
fi
if ${NEED_MTREE}; then
	if yesno "You have created new directories. Run mtree to set" \
	         "permissions"
	then
		(cd "${DESTDIR:-/}" && \
		    mtree -Udef "${DESTDIR}/etc/mtree/NetBSD.dist")
	fi
fi
if ${NEED_MAKEDEV}; then
	makedev_cmd="($(shell_quote cd "${DESTDIR}/dev") && ./MAKEDEV all)"
	if yesno "Do you want to rebuild the device nodes in ${DESTDIR}/dev"
	then
		verbose "Running MAKEDEV in /dev"
		eval "${makedev_cmd}"
	else
		echo ""
		echo "*** You SHOULD rebuild the device nodes in" \
		     "${DESTDIR}/dev"
		echo "*** This is done by running \"${makedev_cmd}\" as root."
		echo ""
	fi
fi
if ${NEED_NEWALIASES}; then
	newaliases_cmd="newaliases"
	# XXX newaliases doesn't work with DESTDIR.
	# We could check whether the system configuration is
	# sufficiently standard, and then run postalias(1) with the
	# right args to make it work, but changes to /etc/mail/aliases
	# are so rare that it doesn't seem worth the effort of checking
	# that the system's mail configuration is standard.
	if [ -z "${DESTDIR}" ] && \
	    yesno "Do you want to rebuild the mail alias database"
	then
		verbose "Running newaliases"
		eval "${newaliases_cmd}"
	else
		echo ""
		echo "*** You MUST rebuild the mail alias database to make" \
		     "the changes visible"
		echo "*** This is done by running \"${newaliases_cmd}\" as root"
		if [ -n "${DESTDIR}" ]; then
		    postalias_cmd="$(shell_quote \
			postalias "hash:${DESTDIR}/etc/mail/aliases")"
		    echo "*** but it won't work with DESTDIR=${DESTDIR}."
		    echo "*** If you use postfix(1) with the default" \
			 "configuration, then you can try"
		    echo "*** running \"${postalias_cmd}\" as root."
		fi
		echo ""
	fi
fi
if [ -x "${DESTDIR}/usr/sbin/postinstall" ]; then
	postinstall_cmd="$(shell_quote "${DESTDIR}/usr/sbin/postinstall" \
	    ${DESTDIR:+-d "${DESTDIR}"}) ${SRC_ARGLIST} check"
	echo "*** Running ${DESTDIR}/usr/sbin/postinstall"
	eval "${postinstall_cmd}"
fi
echo "*** All done"
