#!/bin/sh # # $NetBSD: etcupdate,v 1.21 2003/11/12 13:31:07 grant 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 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"