#!/bin/sh
#
# $NetBSD: etcupdate,v 1.23 2003/12/03 04:51:43 martti Exp $
#
# Copyright (c) 2001 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.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#        This product includes software developed by the NetBSD
#        Foundation, Inc. and its contributors.
# 4. Neither the name of The NetBSD Foundation nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# 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
TEMPROOT="${TEMPROOT:=/tmp/temproot}"
SRCDIR="${SRCDIR:=/usr/src/etc}"
PAGER="${PAGER:=/usr/bin/more}"
SWIDTH=`stty -a | awk '/columns/{w=$6}END{if(w==0){w=80}print w}'`
WIDTH="${WIDTH:=${SWIDTH}}"
VERBOSE=
CONTINUE=
BINARY=
AUTOMATIC=
LOCALSKIP=
MACHINE="${MACHINE:=`uname -m`}"
export MACHINE
MACHINE_ARCH="${MACHINE_ARCH:=`uname -p`}"
export MACHINE_ARCH

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

usage() {
	cat << EOF

Usage: `basename $0` [options]

Options:

  -b srcdir    Location of the extracted sets
  -p pager     Which pager to use              (default: /usr/bin/more)
  -s srcdir    Location of the source files    (default: /usr/src/etc)
  -t temproot  Where to store temporary files  (default: /tmp/temproot)
  -w width     Screen width                    (default: 80)

  -a           Automatically update unmodified files
  -l           Automatically skip files with strictly local changes
               (this option has no effect on files lacking RCS Ids)
  -h           This help text
  -v           Be more verbose

EOF
	exit 1
}

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

	[ ! -z "${VERBOSE}" ] && echo ${*}
}

yesno() {
	# $* = message to display

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

install_dir() {
	# $1 = target directory

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

install_file() {
	# $1 = target file

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

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

install_checksum() {
	# $1 = target file

	[ "${AUTOMATIC}" != "YES" ] && return

	D=`dirname "${1}"`
	mkdir -p "/var/etcupdate/${D}"
	md5 "${1}" > "/var/etcupdate/${1}"
}

diff_and_merge_file() {
	# $1 = target file

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

	if [ "${AUTOMATIC}" = "YES" -a -f "/var/etcupdate/${1}"  ] ; then
		SUM1=`md5 "${1}"`
		SUM2=`cat "/var/etcupdate/${1}"`
		if [ "${SUM1}" = "${SUM2}" ] ; then
			install_file "${1}"
			install_checksum "${1}"
			return
		fi
	fi

	if [ "${LOCALSKIP}" = "YES" ] ; then
		ID1=`ident -q "${TEMPROOT}${1}" | sed -n 2p`
		ID1="${ID1:-0}"
		ID2=`ident -q "${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 "${1}" ]; then
		verbose "===> ${1} (missing)"
		DOES_EXIST=
	else
		verbose "===> ${1} (modified)"
		verbose ""
		DOES_EXIST=YES
		diff -u "${1}" "${TEMPROOT}${1}" | ${PAGER}
	fi

	STAY_HERE=YES
	ALREADY_MERGED=

	# 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 [ "x${STAY_HERE}" = "xYES" ]; do

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

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

EOF
		else
			cat << EOF
  d  Don't install the new file
  i  Install the new file (overwrites your modifications!)
  m  Merge again the currently installed and new files
  s  Show the differences between the currently installed and new files
  u  Undo merge and restore the temporary file from backup
  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=NO
			;;
		[iI])
			install_file "${1}"
			if [ -z "${ALREADY_MERGED}" ]; then
				install_checksum "${1}"
			fi
			STAY_HERE=NO
			;;
		[mM])
			[ -z "${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	\
				"${1}" "${TEMPROOT}${1}"
			mv -f "${TEMPROOT}${1}.merged" "${TEMPROOT}${1}"
			ALREADY_MERGED=YES
			;;
		[sS])
			[ -z "${DOES_EXIST}" ] && continue
			diff -u "${1}" "${TEMPROOT}${1}" | ${PAGER}
			;;
		[uU])
			if [ -f "${B}" ]; then
				echo "*** Restoring ${F}"
				mv -f "${B}" "${F}"
			fi
			ALREADY_MERGED=
			;;
		[vV])
			${PAGER} "${TEMPROOT}${1}"
			;;
		"")
			STAY_HERE=NO
			;;
		*)
			echo "*** Invalid selection!"
			;;
		esac
	done
	rm -f "._etcupdate_${TEMPROOT}${1}"
}

#
# main()
#

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

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

# Read command line arguments
ARGV=`getopt ab:hlp:s:t:vw: $*`
[ $? != 0 ] && usage
set -- ${ARGV}
for i; do
	case "${i}" in
	-a)
		AUTOMATIC=YES
		shift
		;;
	-b)
		BINARY=YES
		SRCDIR="${2}"
		shift 2
		;;
	-h)
		usage
		;;
	-l)
		LOCALSKIP=YES
		shift
		;;
	-p)
		PAGER="${2}"
		shift 2
		;;
	-s)
		SRCDIR="${2}"
		shift 2
		;;
	-t)
		TEMPROOT="${2}"
		shift 2
		;;
	-v)
		VERBOSE=YES
		shift
		;;
	-w)
		WIDTH="${2}"
		shift 2
		;;
	--)
		shift
		break
		;;
	esac
done

# Last minute sanity checks
if [ `id -u` -ne 0 ]; then
	echo "*** ERROR: You MUST be root"
	exit 1
fi
if [ ! -z "${BINARY}" ]; then
	TEMPROOT="${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 [ -z "${BINARY}" -a -r "${TEMPROOT}" ]; then
	echo ""
	echo "*** WARNING: ${TEMPROOT} already exists"
	echo ""
	if yesno "Continue previously aborted update"; then
		CONTINUE=YES
	elif yesno "Remove the old ${TEMPROOT}"; then
		echo "*** Removing ${TEMPROOT}"
		rm -rf "${TEMPROOT}"
	fi
fi

if [ -z "${CONTINUE}" ]; then
	# Are we using the sources or binaries?
	if [ -z "${BINARY}" ]; 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

		# Populate ${TEMPROOT} from ${SRCDIR}
		if [ ! -f "${SRCDIR}/Makefile" ]; then
			echo "*** ERROR: Unable to find ${SRCDIR}/Makefile"
			exit 1
		fi
		# Set the environment for make
		MAKE_ENV=" 			\
			DESTDIR=${TEMPROOT}	\
			MAKE=make		\
			MTREE=mtree		\
			TOOL_MTREE=mtree	\
			INSTALL_DONE=1		\
			USETOOLS=never"
		echo "*** Populating ${TEMPROOT} from ${SRCDIR}"
		cd ${SRCDIR}
		if [ -z "${VERBOSE}" ]; then
			eval ${MAKE_ENV} make distribution > /dev/null
		else
			eval ${MAKE_ENV} make distribution
		fi
		[ $? -ne 0 ] && exit 1
	elif [ ! -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 comparision
	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"
	for i in `find ${TEMPROOT} -type d`; do
		D=`echo ${i} | sed "s#${TEMPROOT}##"`
		[ "x${i}" = "x${TEMPROOT}" ] && continue
		[ ! -d "${D}" ] && install_dir "${D}"
	done
fi

# Start the comparision
echo "*** Checking for added/modified files"
for i in `find ${TEMPROOT} -type f  -a ! -name \*.etcupdate.\*`; do
	D=`echo ${i} | sed "s#${TEMPROOT}##"`
	diff_and_merge_file "${D}"
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 ""
	for i in ${REMAINING}; do
		echo "  ${i}"
	done
	echo ""
fi
if yesno "Remove ${TEMPROOT}"; then
	echo "*** Removing ${TEMPROOT}"
	rm -rf "${TEMPROOT}"
else
	echo "*** Keeping ${TEMPROOT}"
fi

# Do some post-installation tasks
if [ ! -z "${NEED_MTREE}" ]; then
	if yesno "You have created new directories. Run mtree to set" \
	         "permissions"
	then
		(cd / && mtree -Udef /etc/mtree/NetBSD.dist)
	fi
fi
if [ ! -z "${NEED_MAKEDEV}" ]; then
	if yesno "Do you want to rebuild the device nodes in /dev"; then
		verbose "Running MAKEDEV in /dev"
		(cd "/dev" && ./MAKEDEV all)
	else
		echo ""
		echo "*** You SHOULD rebuild the device nodes in /dev"
		echo "*** This is done by running \"(cd /dev &&" \
		     "./MAKEDEV all)\" as root".
		echo ""
	fi
fi
if [ ! -z "${NEED_NEWALIASES}" ]; then
	if yesno "Do you want to rebuild the mail alias database"; then
		verbose "Running newaliases"
		newaliases
	else
		echo ""
		echo "*** You MUST rebuild the mail alias database to make" \
		     "the changes visible"
		echo "*** This is done by running \"newaliases\" as root"
		echo ""
	fi
fi
if [ ! -z "${NEED_PWD_MKDB}" ]; then
	if yesno "Do you want to rebuild the password databases from the" \
	         "new master.passwd"
	then
		verbose "Running pwd_mkdb"
		pwd_mkdb -p "/etc/master.passwd"
	else
		echo ""
		echo "*** You MUST rebuild the password databases to make" \
		     "the changes visible"
		echo "*** This is done by running \"pwd_mkdb -p" \
		     "/etc/master.passwd\" as root"
		echo ""
	fi
fi
if [ -x /etc/postinstall -a -z "${BINARY}" ]; then
	S=`echo ${SRCDIR} | sed 's+/etc++'`
	echo "*** Running /etc/postinstall"
	/etc/postinstall -s "${S}" check
fi
echo "*** All done"
