#!/bin/sh # mergemaster # Compare files created by /usr/src/etc/Makefile (or the directory # the user specifies) with the currently installed copies. # Copyright (c) 1998-2012 Douglas Barton, All rights reserved # Please see detailed copyright below # $FreeBSD: stable/11/usr.sbin/mergemaster/mergemaster.sh 302912 2016-07-15 19:58:05Z bdrewery $ setglobal PATH = '/bin:/usr/bin:/usr/sbin' proc display_usage { setglobal VERSION_NUMBER = $[grep "[$]FreeBSD:" $0 | cut -d ' ' -f 4] echo "mergemaster version $(VERSION_NUMBER)" echo 'Usage: mergemaster [-scrvhpCP] [-a|[-iFU]] [--run-updates=always|never]' echo ' [-m /path] [-t /path] [-d] [-u N] [-w N] [-A arch] [-D /path]' echo "Options:" echo " -s Strict comparison (diff every pair of files)" echo " -c Use context diff instead of unified diff" echo " -r Re-run on a previously cleaned directory (skip temproot creation)" echo " -v Be more verbose about the process, include additional checks" echo " -a Leave all files that differ to merge by hand" echo " -h Display more complete help" echo ' -i Automatically install files that do not exist in destination directory' echo ' -p Pre-buildworld mode, only compares crucial files' echo ' -F Install files that differ only by revision control Id ($FreeBSD)' echo ' -C Compare local rc.conf variables to the defaults' echo ' -P Preserve files that are overwritten' echo " -U Attempt to auto upgrade files that have not been user modified" echo ' ***DANGEROUS***' echo ' --run-updates= Specify always or never to run newalises, pwd_mkdb, etc.' echo '' echo " -m /path/directory Specify location of source to do the make in" echo " -t /path/directory Specify temp root directory" echo " -d Add date and time to directory name (e.g., /var/tmp/temproot.$[date +%m%d.%H.%M])" echo " -u N Specify a numeric umask" echo " -w N Specify a screen width in columns to sdiff" echo " -A architecture Alternative architecture name to pass to make" echo ' -D /path/directory Specify the destination directory to install files to' echo '' } proc display_help { echo "* To specify a directory other than /var/tmp/temproot for the" echo " temporary root environment, use -t /path/to/temp/root" echo "* The -w option takes a number as an argument for the column width" echo " of the screen. The default is 80." echo '* The -a option causes mergemaster to run without prompting.' } # Loop allowing the user to use sdiff to merge files and display the merged # file. proc merge_loop { match $(VERBOSE) { with '' with * echo " *** Type h at the sdiff prompt (%) to get usage help" } echo '' setglobal MERGE_AGAIN = 'yes' while test $(MERGE_AGAIN) = "yes" { # Prime file.merged so we don't blat the owner/group id's cp -p $(COMPFILE) "$(COMPFILE).merged" sdiff -o "$(COMPFILE).merged" --text --suppress-common-lines \ --width=$(SCREEN_WIDTH:-80) "$(DESTDIR)$(COMPFILE#.)" $(COMPFILE) setglobal INSTALL_MERGED = 'V' while test $(INSTALL_MERGED) = "v" -o $(INSTALL_MERGED) = "V" { echo '' echo " Use 'i' to install merged file" echo " Use 'r' to re-do the merge" echo " Use 'v' to view the merged file" echo " Default is to leave the temporary file to deal with by hand" echo '' echo -n " *** How should I deal with the merged file? [Leave it for later] " read INSTALL_MERGED match $(INSTALL_MERGED) { with [iI] mv "$(COMPFILE).merged" $(COMPFILE) echo '' if mm_install $(COMPFILE) { echo " *** Merged version of $(COMPFILE) installed successfully" } else { echo " *** Problem installing $(COMPFILE), it will remain to merge by hand later" } unset MERGE_AGAIN with [rR] rm "$(COMPFILE).merged" with [vV] $(PAGER) "$(COMPFILE).merged" with '' echo " *** $(COMPFILE) will remain for your consideration" unset MERGE_AGAIN with * echo "invalid choice: $(INSTALL_MERGED)" setglobal INSTALL_MERGED = 'V' } } } } # Loop showing user differences between files, allow merge, skip or install # options proc diff_loop { setglobal HANDLE_COMPFILE = 'v' while test $(HANDLE_COMPFILE) = "v" -o $(HANDLE_COMPFILE) = "V" -o \ $(HANDLE_COMPFILE) = "NOT V" { if test -f "$(DESTDIR)$(COMPFILE#.)" -a -f $(COMPFILE) { if test -n $(AUTO_UPGRADE) -a -n $(CHANGED) { match $(CHANGED) { with *:${DESTDIR}${COMPFILE#.}:* # File has been modified with * echo '' echo " *** $(COMPFILE) has not been user modified." echo '' if mm_install $(COMPFILE) { echo " *** $(COMPFILE) upgraded successfully" echo '' # Make the list print one file per line setglobal AUTO_UPGRADED_FILES = ""$(AUTO_UPGRADED_FILES) $(DESTDIR)$(COMPFILE#.) "" } else { echo " *** Problem upgrading $(COMPFILE), it will remain to merge by hand" } return } } if test $(HANDLE_COMPFILE) = "v" -o $(HANDLE_COMPFILE) = "V" { echo '' echo ' ====================================================================== ' echo '' shell { echo " *** Displaying differences between $(COMPFILE) and installed version:" echo '' diff $(DIFF_FLAG) $(DIFF_OPTIONS) "$(DESTDIR)$(COMPFILE#.)" $(COMPFILE) } | $(PAGER) echo '' } } else { echo '' echo " *** There is no installed version of $(COMPFILE)" echo '' match $(AUTO_INSTALL) { with [Yy][Ee][Ss] echo '' if mm_install $(COMPFILE) { echo " *** $(COMPFILE) installed successfully" echo '' # Make the list print one file per line setglobal AUTO_INSTALLED_FILES = ""$(AUTO_INSTALLED_FILES) $(DESTDIR)$(COMPFILE#.) "" } else { echo " *** Problem installing $(COMPFILE), it will remain to merge by hand" } return with * setglobal NO_INSTALLED = 'yes' } } echo " Use 'd' to delete the temporary $(COMPFILE)" echo " Use 'i' to install the temporary $(COMPFILE)" match $(NO_INSTALLED) { with '' echo " Use 'm' to merge the temporary and installed versions" echo " Use 'v' to view the diff results again" } echo '' echo " Default is to leave the temporary file to deal with by hand" echo '' echo -n "How should I deal with this? [Leave it for later] " read HANDLE_COMPFILE match $(HANDLE_COMPFILE) { with [dD] rm $(COMPFILE) echo '' echo " *** Deleting $(COMPFILE)" with [iI] echo '' if mm_install $(COMPFILE) { echo " *** $(COMPFILE) installed successfully" } else { echo " *** Problem installing $(COMPFILE), it will remain to merge by hand" } with [mM] match $(NO_INSTALLED) { with '' # interact with user to merge files merge_loop with * echo '' echo " *** There is no installed version of $(COMPFILE)" echo '' setglobal HANDLE_COMPFILE = '"NOT V'" } # End of "No installed version of file but user selected merge" test with [vV] continue with '' echo '' echo " *** $(COMPFILE) will remain for your consideration" with * # invalid choice, show menu again. echo "invalid choice: $(HANDLE_COMPFILE)" echo '' setglobal HANDLE_COMPFILE = '"NOT V'" continue } # End of "How to handle files that are different" } unset NO_INSTALLED echo '' match $(VERBOSE) { with '' with * sleep 3 } } proc press_to_continue { var DISCARD = '' echo -n ' *** Press the [Enter] or [Return] key to continue ' read DISCARD } # Set the default path for the temporary root environment # setglobal TEMPROOT = ''/var/tmp/temproot'' # Read /etc/mergemaster.rc first so the one in $HOME can override # if test -r /etc/mergemaster.rc { source /etc/mergemaster.rc } # Read .mergemasterrc before command line so CLI can override # if test -r "$HOME/.mergemasterrc" { source "$HOME/.mergemasterrc" } for var in [@Argv] { match $var { with --run-updates* setglobal RUN_UPDATES = $[echo $(var#--run-updates=) | tr [:upper:] [:lower:]] with * setglobal newopts = ""$newopts $var"" } } set -- $newopts unset var newopts # Check the command line options # while getopts ":ascrvhipCPm:t:du:w:D:A:FU" COMMAND_LINE_ARGUMENT { match $(COMMAND_LINE_ARGUMENT) { with A setglobal ARCHSTRING = "'TARGET_ARCH='$(OPTARG)" with F setglobal FREEBSD_ID = 'yes' with U setglobal AUTO_UPGRADE = 'yes' with s setglobal STRICT = 'yes' unset DIFF_OPTIONS with c setglobal DIFF_FLAG = ''-c'' with r setglobal RERUN = 'yes' with v match $(AUTO_RUN) { with '' setglobal VERBOSE = 'yes' } with a setglobal AUTO_RUN = 'yes' unset VERBOSE with h display_usage display_help exit 0 with i setglobal AUTO_INSTALL = 'yes' with C setglobal COMP_CONFS = 'yes' with P setglobal PRESERVE_FILES = 'yes' with p setglobal PRE_WORLD = 'yes' unset COMP_CONFS unset AUTO_RUN with m setglobal SOURCEDIR = $(OPTARG) with t setglobal TEMPROOT = $(OPTARG) with d setglobal TEMPROOT = "$(TEMPROOT).$[date +%m%d.%H.%M]" with u setglobal NEW_UMASK = $(OPTARG) with w setglobal SCREEN_WIDTH = $(OPTARG) with D setglobal DESTDIR = $(OPTARG) with * display_usage exit 1 } } if test -n $AUTO_RUN { if test -n $FREEBSD_ID -o -n $AUTO_UPGRADE -o -n $AUTO_INSTALL { echo '' echo "*** You have included the -a option along with one or more options" echo ' that indicate that you wish mergemaster to actually make updates' echo ' (-F, -U, or -i), however these options are not compatible.' echo ' Please read mergemaster(8) for more information.' echo '' exit 1 } } # Assign the location of the mtree database # setglobal MTREEDB = $(MTREEDB:-${DESTDIR}/var/db) setglobal MTREEFILE = ""$(MTREEDB)/mergemaster.mtree"" # Don't force the user to set this in the mergemaster rc file if test -n $(PRESERVE_FILES) -a -z $(PRESERVE_FILES_DIR) { setglobal PRESERVE_FILES_DIR = "/var/tmp/mergemaster/preserved-files-$[date +%y%m%d-%H%M%S]" mkdir -p $(PRESERVE_FILES_DIR) } # Check for the mtree database in DESTDIR match $(AUTO_UPGRADE) { with '' # If the option is not set no need to run the test or warn the user with * if test ! -s $(MTREEFILE) { echo '' echo "*** Unable to find mtree database ($(MTREEFILE))." echo " Skipping auto-upgrade on this run." echo " It will be created for the next run when this one is complete." echo '' match $(AUTO_RUN) { with '' press_to_continue } unset AUTO_UPGRADE } } if test -e "$(DESTDIR)/etc/fstab" { if grep -q nodev $(DESTDIR)/etc/fstab { echo '' echo "*** You have the deprecated 'nodev' option in $(DESTDIR)/etc/fstab." echo " This can prevent the filesystem from being mounted on reboot." echo " Please update your fstab before continuing." echo " See fstab(5) for more information." echo '' exit 1 } } echo '' # If the user has a pager defined, make sure we can run it # match $(DONT_CHECK_PAGER) { with '' proc check_pager { while ! type $(PAGER%% *) >/dev/null { echo " *** Your PAGER environment variable specifies '$(PAGER)', but" echo " due to the limited PATH that I use for security reasons," echo " I cannot execute it. So, what would you like to do?" echo '' echo " Use 'e' to exit mergemaster and fix your PAGER variable" echo " Use 'l' to set PAGER to 'less' for this run" echo " Use 'm' to use plain old 'more' as your PAGER for this run" echo '' echo " or you may type an absolute path to PAGER for this run" echo '' echo " Default is to use plain old 'more' " echo '' echo -n "What should I do? [Use 'more'] " read FIXPAGER match $(FIXPAGER) { with [eE] exit 0 with [lL] setglobal PAGER = 'less' with [mM]|'' setglobal PAGER = 'more' with /* setglobal PAGER = $FIXPAGER with * echo '' echo "invalid choice: $(FIXPAGER)" } echo '' } } if test -n $(PAGER) { check_pager } } # If user has a pager defined, or got assigned one above, use it. # If not, use more. # setglobal PAGER = $(PAGER:-more) if test -n $(VERBOSE) -a ! $(PAGER) = "more" { echo " *** You have $(PAGER) defined as your pager so we will use that" echo '' sleep 3 } # Assign the diff flag once so we will not have to keep testing it # setglobal DIFF_FLAG = $(DIFF_FLAG:--u) # Assign the source directory # setglobal SOURCEDIR = $(SOURCEDIR:-/usr/src) if test ! -f $(SOURCEDIR)/Makefile.inc1 -a \ -f $(SOURCEDIR)/../Makefile.inc1 { echo " *** The source directory you specified ($(SOURCEDIR))" echo " will be reset to $(SOURCEDIR)/.." echo '' sleep 3 setglobal SOURCEDIR = "$(SOURCEDIR)/.." } setglobal SOURCEDIR = $[realpath $SOURCEDIR] # Setup make to use system files from SOURCEDIR setglobal MM_MAKE = ""make $(ARCHSTRING) -m $(SOURCEDIR)/share/mk -DNO_FILEMON"" # Check DESTDIR against the mergemaster mtree database to see what # files the user changed from the reference files. # if test -n $(AUTO_UPGRADE) -a -s $(MTREEFILE) { # Force FreeBSD 9 compatible output when available. if mtree -F freebsd9 -c -p /var/empty/ > /dev/null !2 > !1 { setglobal MTREE_FLAVOR = '"-F freebsd9'" } else { setglobal MTREE_FLAVOR = '' } setglobal CHANGED = ':' for file in [$[mtree -eqL $(MTREE_FLAVOR) -f $(MTREEFILE) -p $(DESTDIR)/ \ !2 >/dev/null | awk '($2 == "changed") {print $1}]] { if test -f "$(DESTDIR)/$file" { setglobal CHANGED = ""$(CHANGED)$(DESTDIR)/$(file):"" } } test $CHANGED = ':' && unset CHANGED } # Check the width of the user's terminal # if test -t 0 { setglobal w = $[tput columns] match $(w) { with 0|'' # No-op, since the input is not valid with * match $(SCREEN_WIDTH) { with '' setglobal SCREEN_WIDTH = $(w) with "${w}" # No-op, since they are the same with * echo -n "*** You entered $(SCREEN_WIDTH) as your screen width, but stty " echo "thinks it is $(w)." echo '' echo -n "What would you like to use? [$(w)] " read SCREEN_WIDTH match $(SCREEN_WIDTH) { with '' setglobal SCREEN_WIDTH = $(w) } } } } # Define what $Id tag to look for to aid portability. # setglobal ID_TAG = 'FreeBSD' proc delete_temproot { rm -rf $(TEMPROOT) !2 >/dev/null chflags -R 0 $(TEMPROOT) !2 >/dev/null rm -rf $(TEMPROOT) || do { echo "*** Unable to delete $(TEMPROOT)"; exit 1; } } match $(RERUN) { with '' # Set up the loop to test for the existence of the # temp root directory. # setglobal TEST_TEMP_ROOT = 'yes' while test $(TEST_TEMP_ROOT) = "yes" { if test -d $(TEMPROOT) { echo "*** The directory specified for the temporary root environment," echo " $(TEMPROOT), exists. This can be a security risk if untrusted" echo " users have access to the system." echo '' match $(AUTO_RUN) { with '' echo " Use 'd' to delete the old $(TEMPROOT) and continue" echo " Use 't' to select a new temporary root directory" echo " Use 'e' to exit mergemaster" echo '' echo " Default is to use $(TEMPROOT) as is" echo '' echo -n "How should I deal with this? [Use the existing $(TEMPROOT)] " read DELORNOT match $(DELORNOT) { with [dD] echo '' echo " *** Deleting the old $(TEMPROOT)" echo '' delete_temproot unset TEST_TEMP_ROOT with [tT] echo " *** Enter new directory name for temporary root environment" read TEMPROOT with [eE] exit 0 with '' echo '' echo " *** Leaving $(TEMPROOT) intact" echo '' unset TEST_TEMP_ROOT with * echo '' echo "invalid choice: $(DELORNOT)" echo '' } with * # If this is an auto-run, try a hopefully safe alternative then # re-test anyway. setglobal TEMPROOT = "/var/tmp/temproot.$[date +%m%d.%H.%M.%S]" } } else { unset TEST_TEMP_ROOT } } echo "*** Creating the temporary root environment in $(TEMPROOT)" if mkdir -p $(TEMPROOT) { echo " *** $(TEMPROOT) ready for use" } if test ! -d $(TEMPROOT) { echo '' echo " *** FATAL ERROR: Cannot create $(TEMPROOT)" echo '' exit 1 } echo " *** Creating and populating directory structure in $(TEMPROOT)" echo '' match $(VERBOSE) { with '' with * press_to_continue } match $(PRE_WORLD) { with '' do { cd $(SOURCEDIR) && match $(DESTDIR) { with '' with * $(MM_MAKE) DESTDIR=$(DESTDIR) distrib-dirs >/dev/null } $(MM_MAKE) DESTDIR=$(TEMPROOT) distrib-dirs >/dev/null && $(MM_MAKE) _obj SUBDIR_OVERRIDE=etc >/dev/null && $(MM_MAKE) everything SUBDIR_OVERRIDE=etc >/dev/null && $(MM_MAKE) DESTDIR=$(TEMPROOT) distribution >/dev/null;} || do { echo ''; echo " *** FATAL ERROR: Cannot 'cd' to $(SOURCEDIR) and install files to"; echo " the temproot environment"; echo ''; exit 1;} with * # Only set up files that are crucial to {build|install}world do { mkdir -p $(TEMPROOT)/etc && cp -p $(SOURCEDIR)/etc/master.passwd $(TEMPROOT)/etc && install -p -o root -g wheel -m 0644 $(SOURCEDIR)/etc/group $(TEMPROOT)/etc;} || do { echo ''; echo ' *** FATAL ERROR: Cannot copy files to the temproot environment'; echo ''; exit 1;} } # Doing the inventory and removing files that we don't want to compare only # makes sense if we are not doing a rerun, since we have no way of knowing # what happened to the files during previous incarnations. match $(VERBOSE) { with '' with * echo '' echo ' *** The following files exist only in the installed version of' echo " $(DESTDIR)/etc. In the vast majority of cases these files" echo ' are necessary parts of the system and should not be deleted.' echo ' However because these files are not updated by this process you' echo ' might want to verify their status before rebooting your system.' echo '' press_to_continue diff -qr $(DESTDIR)/etc $(TEMPROOT)/etc | grep "^Only in $(DESTDIR)/etc" | $(PAGER) echo '' press_to_continue } match $(IGNORE_MOTD) { with '' with * echo '' echo "*** You have the IGNORE_MOTD option set in your mergemaster rc file." echo " This option is deprecated in favor of the IGNORE_FILES option." echo " Please update your rc file accordingly." echo '' exit 1 } # Avoid comparing the following user specified files for file in [$(IGNORE_FILES)] { test -e $(TEMPROOT)/$(file) && unlink $(TEMPROOT)/$(file) } # We really don't want to have to deal with files like login.conf.db, pwd.db, # or spwd.db. Instead, we want to compare the text versions, and run *_mkdb. # Prompt the user to do so below, as needed. # rm -f $(TEMPROOT)/etc/*.db $(TEMPROOT)/etc/passwd \ $(TEMPROOT)/var/db/services.db # We only need to compare things like freebsd.cf once find $(TEMPROOT)/usr/obj -type f -delete !2 >/dev/null # Delete stuff we do not need to keep the mtree database small, # and to make the actual comparison faster. find $(TEMPROOT)/usr -type l -delete !2 >/dev/null find $(TEMPROOT) -type f -size 0 -delete !2 >/dev/null find -d $(TEMPROOT) -type d -empty -mindepth 1 -delete !2 >/dev/null # Build the mtree database in a temporary location. match $(PRE_WORLD) { with '' setglobal MTREENEW = $[mktemp -t mergemaster.mtree] mtree -nci -p $(TEMPROOT) -k size,md5digest > $(MTREENEW) !2 >/dev/null with * # We don't want to mess with the mtree database on a pre-world run or # when re-scanning a previously-built tree. } # End of the "RERUN" test } # Get ready to start comparing files # Check umask if not specified on the command line, # and we are not doing an autorun # if test -z $(NEW_UMASK) -a -z $(AUTO_RUN) { setglobal USER_UMASK = $[umask] match $(USER_UMASK) { with 0022|022 with * echo '' echo " *** Your umask is currently set to $(USER_UMASK). By default, this script" echo " installs all files with the same user, group and modes that" echo " they are created with by $(SOURCEDIR)/etc/Makefile, compared to" echo " a umask of 022. This umask allows world read permission when" echo " the file's default permissions have it." echo '' echo " No world permissions can sometimes cause problems. A umask of" echo " 022 will restore the default behavior, but is not mandatory." echo " /etc/master.passwd is a special case. Its file permissions" echo " will be 600 (rw-------) if installed." echo '' echo -n "What umask should I use? [$(USER_UMASK)] " read NEW_UMASK setglobal NEW_UMASK = $(NEW_UMASK:-$USER_UMASK) } echo '' } setglobal CONFIRMED_UMASK = $(NEW_UMASK:-0022) # # Warn users who still have old rc files # for file in [atm devfs diskless1 diskless2 network network6 pccard \ serial syscons sysctl alpha amd64 i386 sparc64] { if test -f "$(DESTDIR)/etc/rc.$(file)" { setglobal OLD_RC_PRESENT = '1' break } } match $(OLD_RC_PRESENT) { with 1 echo '' echo " *** There are elements of the old rc system in $(DESTDIR)/etc/." echo '' echo ' While these scripts will not hurt anything, they are not' echo ' functional on an up to date system, and can be removed.' echo '' match $(AUTO_RUN) { with '' echo -n 'Move these files to /var/tmp/mergemaster/old_rc? [yes] ' read MOVE_OLD_RC match $(MOVE_OLD_RC) { with [nN]* with * mkdir -p /var/tmp/mergemaster/old_rc for file in [atm devfs diskless1 diskless2 network network6 pccard \ serial syscons sysctl alpha amd64 i386 sparc64] { if test -f "$(DESTDIR)/etc/rc.$(file)" { mv $(DESTDIR)/etc/rc.$(file) /var/tmp/mergemaster/old_rc/ } } echo ' The files have been moved' press_to_continue } with * } } # Use the umask/mode information to install the files # Create directories as needed # proc install_error { echo "*** FATAL ERROR: Unable to install $(1) to $(2)" echo '' exit 1 } proc do_install_and_rm { match $(PRESERVE_FILES) { with [Yy][Ee][Ss] if test -f "$(3)/$(2##*/)" { mkdir -p $(PRESERVE_FILES_DIR)/$(2%/*) cp $(3)/$(2##*/) $(PRESERVE_FILES_DIR)/$(2%/*) } } if test ! -d "$(3)/$(2##*/)" { if install -m $(1) $(2) $(3) { unlink $(2) } else { install_error $(2) $(3) } } else { install_error $(2) $(3) } } # 4095 = "obase=10;ibase=8;07777" | bc proc find_mode { var OCTAL = '' set OCTAL = $shExpr(' ~$(echo "obase=10; ibase=8; ${CONFIRMED_UMASK}" | bc) & 4095 & $(echo "obase=10; ibase=8; $(stat -f "%OMp%OLp" ${1})" | bc) ') printf "%04o\n" $(OCTAL) } proc mm_install { var INSTALL_DIR = '' set INSTALL_DIR = $(1#.) set INSTALL_DIR = $(INSTALL_DIR%/*) match $(INSTALL_DIR) { with '' set INSTALL_DIR = '/' } if test -n "$(DESTDIR)$(INSTALL_DIR)" -a ! -d "$(DESTDIR)$(INSTALL_DIR)" { setglobal DIR_MODE = $[find_mode "$(TEMPROOT)/$(INSTALL_DIR)] install -d -o root -g wheel -m $(DIR_MODE) "$(DESTDIR)$(INSTALL_DIR)" || install_error $1 $(DESTDIR)$(INSTALL_DIR) } setglobal FILE_MODE = $[find_mode $(1)] if test ! -x $(1) { match $(1#.) { with /etc/mail/aliases setglobal NEED_NEWALIASES = 'yes' with /etc/login.conf setglobal NEED_CAP_MKDB = 'yes' with /etc/services setglobal NEED_SERVICES_MKDB = 'yes' with /etc/master.passwd do_install_and_rm 600 $(1) "$(DESTDIR)$(INSTALL_DIR)" setglobal NEED_PWD_MKDB = 'yes' setglobal DONT_INSTALL = 'yes' with /.cshrc | /.profile var st_nlink = '' # install will unlink the file before it installs the new one, # so we have to restore/create the link afterwards. # set st_nlink = '0' # In case the file does not yet exist eval $[stat -s $(DESTDIR)$(COMPFILE#.) !2 >/dev/null] do_install_and_rm $(FILE_MODE) $(1) "$(DESTDIR)$(INSTALL_DIR)" if test -n $(AUTO_INSTALL) -a $st_nlink -gt 1 { setglobal HANDLE_LINK = 'l' } else { match $(LINK_EXPLAINED) { with '' echo " *** Historically BSD derived systems have had a" echo " hard link from /.cshrc and /.profile to" echo " their namesakes in /root. Please indicate" echo " your preference below for bringing your" echo " installed files up to date." echo '' setglobal LINK_EXPLAINED = 'yes' } echo " Use 'd' to delete the temporary $(COMPFILE)" echo " Use 'l' to delete the existing $(DESTDIR)/root/$(COMPFILE##*/) and create the link" echo '' echo " Default is to leave the temporary file to deal with by hand" echo '' echo -n " How should I handle $(COMPFILE)? [Leave it to install later] " read HANDLE_LINK } match $(HANDLE_LINK) { with [dD]* rm $(COMPFILE) echo '' echo " *** Deleting $(COMPFILE)" with [lL]* echo '' unlink $(DESTDIR)/root/$(COMPFILE##*/) if ln $(DESTDIR)$(COMPFILE#.) $(DESTDIR)/root/$(COMPFILE##*/) { echo " *** Link from $(DESTDIR)$(COMPFILE#.) to $(DESTDIR)/root/$(COMPFILE##*/) installed successfully" } else { echo " *** Error linking $(DESTDIR)$(COMPFILE#.) to $(DESTDIR)/root/$(COMPFILE##*/)" echo " *** $(COMPFILE) will remain for your consideration" } with * echo " *** $(COMPFILE) will remain for your consideration" } return } match $(DONT_INSTALL) { with '' do_install_and_rm $(FILE_MODE) $(1) "$(DESTDIR)$(INSTALL_DIR)" with * unset DONT_INSTALL } } else { # File matched -x do_install_and_rm $(FILE_MODE) $(1) "$(DESTDIR)$(INSTALL_DIR)" } return $? } if test ! -d $(TEMPROOT) { echo "*** FATAL ERROR: The temproot directory ($(TEMPROOT))" echo ' has disappeared!' echo '' exit 1 } echo '' echo "*** Beginning comparison" echo '' # Pre-world does not populate /etc/rc.d. # It is very possible that a previous run would have deleted files in # ${TEMPROOT}/etc/rc.d, thus creating a lot of false positives. if test -z $(PRE_WORLD) -a -z $(RERUN) { echo " *** Checking $(DESTDIR)/etc/rc.d for stale files" echo '' cd "$(DESTDIR)/etc/rc.d" && for file in [*] { if test ! -e "$(TEMPROOT)/etc/rc.d/$(file)" { setglobal STALE_RC_FILES = ""$(STALE_RC_FILES) $(file)"" } } match $(STALE_RC_FILES) { with ''|' *' echo ' *** No stale files found' with * echo " *** The following files exist in $(DESTDIR)/etc/rc.d but not in" echo " $(TEMPROOT)/etc/rc.d/:" echo '' echo $(STALE_RC_FILES) echo '' echo ' The presence of stale files in this directory can cause the' echo ' dreaded unpredictable results, and therefore it is highly' echo ' recommended that you delete them.' match $(AUTO_RUN) { with '' echo '' echo -n ' *** Delete them now? [n] ' read DELETE_STALE_RC_FILES match $(DELETE_STALE_RC_FILES) { with [yY] echo ' *** Deleting ... ' rm $(STALE_RC_FILES) echo ' done.' with * echo ' *** Files will not be deleted' } sleep 2 with * if test -n $(DELETE_STALE_RC_FILES) { echo ' *** Deleting ... ' rm $(STALE_RC_FILES) echo ' done.' } } } echo '' } cd $(TEMPROOT) if test -r $(MM_PRE_COMPARE_SCRIPT) { source "${MM_PRE_COMPARE_SCRIPT}" } # Things that were files/directories/links in one version can sometimes # change to something else in a newer version. So we need to explicitly # test for this, and warn the user if what we find does not match. # for COMPFILE in [$[find . | sort]] { if test -e "$(DESTDIR)$(COMPFILE#.)" { setglobal INSTALLED_TYPE = $[stat -f '%HT' $(DESTDIR)$(COMPFILE#.)] } else { continue } setglobal TEMPROOT_TYPE = $[stat -f '%HT' $COMPFILE] if test ! $TEMPROOT_TYPE = $INSTALLED_TYPE { test $COMPFILE = '.' && continue setglobal TEMPROOT_TYPE = $[echo $TEMPROOT_TYPE | tr [:upper:] [:lower:]] setglobal INSTALLED_TYPE = $[echo $INSTALLED_TYPE | tr [:upper:] [:lower:]] echo "*** The installed file $(DESTDIR)$(COMPFILE#.) has the type \"$INSTALLED_TYPE\"" echo " but the new version has the type \"$TEMPROOT_TYPE\"" echo '' echo " How would you like to handle this?" echo '' echo " Use 'r' to remove $(DESTDIR)$(COMPFILE#.)" match $TEMPROOT_TYPE { with 'symbolic link' setglobal TARGET = $[readlink $COMPFILE] echo " and create a link to $TARGET in its place" with * echo " You will be able to install it as a \"$TEMPROOT_TYPE\"" } echo '' echo " Use 'i' to ignore this" echo '' echo -n " How to proceed? [i] " read ANSWER match $ANSWER { with [rR] match $(PRESERVE_FILES) { with [Yy][Ee][Ss] mv $(DESTDIR)$(COMPFILE#.) $(PRESERVE_FILES_DIR)/ || exit 1 with * rm -rf $(DESTDIR)$(COMPFILE#.) } match $TEMPROOT_TYPE { with 'symbolic link' ln -sf $TARGET $(DESTDIR)$(COMPFILE#.) } with * echo '' echo "*** See the man page about adding $(COMPFILE#.) to the list of IGNORE_FILES" press_to_continue } echo '' } } for COMPFILE in [$[find . -type f | sort]] { # First, check to see if the file exists in DESTDIR. If not, the # diff_loop function knows how to handle it. # if test ! -e "$(DESTDIR)$(COMPFILE#.)" { match $(AUTO_RUN) { with '' diff_loop with * match $(AUTO_INSTALL) { with '' # If this is an auto run, make it official echo " *** $(COMPFILE) will remain for your consideration" with * diff_loop } } # Auto run test continue } match $(STRICT) { with '' | [Nn][Oo] # Compare $Id's first so if the file hasn't been modified # local changes will be ignored. # If the files have the same $Id, delete the one in temproot so the # user will have less to wade through if files are left to merge by hand. # setglobal ID1 = $[grep "[$]$(ID_TAG):" $(DESTDIR)$(COMPFILE#.) !2 >/dev/null] setglobal ID2 = $[grep "[$]$(ID_TAG):" $(COMPFILE) !2 >/dev/null] || setglobal ID2 = 'none' match $(ID2) { with "${ID1}" echo " *** Temp $(COMPFILE) and installed have the same Id, deleting" rm $(COMPFILE) } } # If the file is still here either because the $Ids are different, the # file doesn't have an $Id, or we're using STRICT mode; look at the diff. # if test -f $(COMPFILE) { # Do an absolute diff first to see if the files are actually different. # If they're not different, delete the one in temproot. # if diff -q $(DIFF_OPTIONS) "$(DESTDIR)$(COMPFILE#.)" $(COMPFILE) > \ /dev/null !2 > !1 { echo " *** Temp $(COMPFILE) and installed are the same, deleting" rm $(COMPFILE) } else { # Ok, the files are different, so show the user where they differ. # Use user's choice of diff methods; and user's pager if they have one. # Use more if not. # Use unified diffs by default. Context diffs give me a headache. :) # # If the user chose the -F option, test for that before proceeding # if test -n $FREEBSD_ID { if diff -q -I'[$]FreeBSD.*[$]' "$(DESTDIR)$(COMPFILE#.)" $(COMPFILE) > \ /dev/null !2 > !1 { if mm_install $(COMPFILE) { echo "*** Updated revision control Id for $(DESTDIR)$(COMPFILE#.)" } else { echo "*** Problem installing $(COMPFILE), it will remain to merge by hand later" } continue } } match $(AUTO_RUN) { with '' # prompt user to install/delete/merge changes diff_loop with * # If this is an auto run, make it official echo " *** $(COMPFILE) will remain for your consideration" } # Auto run test } # Yes, the files are different } # Yes, the file still remains to be checked } # This is for the for way up there at the beginning of the comparison echo '' echo "*** Comparison complete" if test -s $(MTREENEW) { echo "*** Saving mtree database for future upgrades" test -e $(MTREEFILE) && unlink $(MTREEFILE) mv $(MTREENEW) $(MTREEFILE) } echo '' setglobal TEST_FOR_FILES = $[find $(TEMPROOT) -type f -size +0 !2 >/dev/null] if test -n $(TEST_FOR_FILES) { echo "*** Files that remain for you to merge by hand:" find $(TEMPROOT) -type f -size +0 | sort echo '' match $(AUTO_RUN) { with '' echo -n "Do you wish to delete what is left of $(TEMPROOT)? [no] " read DEL_TEMPROOT match $(DEL_TEMPROOT) { with [yY]* delete_temproot with * echo " *** $(TEMPROOT) will remain" } with * } } else { echo "*** $(TEMPROOT) is empty, deleting" delete_temproot } match $(AUTO_INSTALLED_FILES) { with '' with * match $(AUTO_RUN) { with '' shell { echo '' echo '*** You chose the automatic install option for files that did not' echo ' exist on your system. The following were installed for you:' echo $(AUTO_INSTALLED_FILES) } | $(PAGER) with * echo '' echo '*** You chose the automatic install option for files that did not' echo ' exist on your system. The following were installed for you:' echo $(AUTO_INSTALLED_FILES) } } match $(AUTO_UPGRADED_FILES) { with '' with * match $(AUTO_RUN) { with '' shell { echo '' echo '*** You chose the automatic upgrade option for files that you did' echo ' not alter on your system. The following were upgraded for you:' echo $(AUTO_UPGRADED_FILES) } | $(PAGER) with * echo '' echo '*** You chose the automatic upgrade option for files that you did' echo ' not alter on your system. The following were upgraded for you:' echo $(AUTO_UPGRADED_FILES) } } proc run_it_now { test -n $AUTO_RUN && return var answer = '' echo '' while : { if test $RUN_UPDATES = always { set answer = 'y' } elif test $RUN_UPDATES = never { set answer = 'n' } else { echo -n ' Would you like to run it now? y or n [n] ' read answer } match $answer { with y echo " Running $(1)" echo '' eval $(1) return with ''|n if test ! $RUN_UPDATES = never { echo '' echo " *** Cancelled" echo '' } echo " Make sure to run $(1) yourself" return with * echo '' echo " *** Sorry, I do not understand your answer ($(answer))" echo '' } } } match $(NEED_NEWALIASES) { with '' with * echo '' if test -n $(DESTDIR) { echo "*** You installed a new aliases file into $(DESTDIR)/etc/mail, but" echo " the newaliases command is limited to the directories configured" echo " in sendmail.cf. Make sure to create your aliases database by" echo " hand when your sendmail configuration is done." } else { echo "*** You installed a new aliases file, so make sure that you run" echo " '/usr/bin/newaliases' to rebuild your aliases database" run_it_now '/usr/bin/newaliases' } } match $(NEED_CAP_MKDB) { with '' with * echo '' echo "*** You installed a login.conf file, so make sure that you run" echo " '/usr/bin/cap_mkdb $(DESTDIR)/etc/login.conf'" echo " to rebuild your login.conf database" run_it_now "/usr/bin/cap_mkdb $(DESTDIR)/etc/login.conf" } match $(NEED_SERVICES_MKDB) { with '' with * echo '' echo "*** You installed a services file, so make sure that you run" echo " '/usr/sbin/services_mkdb -q -o $(DESTDIR)/var/db/services.db $(DESTDIR)/etc/services'" echo " to rebuild your services database" run_it_now "/usr/sbin/services_mkdb -q -o $(DESTDIR)/var/db/services.db $(DESTDIR)/etc/services" } match $(NEED_PWD_MKDB) { with '' with * echo '' echo "*** You installed a new master.passwd file, so make sure that you run" if test -n $(DESTDIR) { echo " '/usr/sbin/pwd_mkdb -d $(DESTDIR)/etc -p $(DESTDIR)/etc/master.passwd'" echo " to rebuild your password files" run_it_now "/usr/sbin/pwd_mkdb -d $(DESTDIR)/etc -p $(DESTDIR)/etc/master.passwd" } else { echo " '/usr/sbin/pwd_mkdb -p /etc/master.passwd'" echo " to rebuild your password files" run_it_now '/usr/sbin/pwd_mkdb -p /etc/master.passwd' } } if test -e "$(DESTDIR)/etc/localtime" -a ! -L "$(DESTDIR)/etc/localtime" -a -z $(PRE_WORLD) { # Ignore if TZ == UTC echo '' test -n $(DESTDIR) && setglobal tzs_args = ""-C $(DESTDIR)"" if test -f "$(DESTDIR)/var/db/zoneinfo" { echo "*** Reinstalling $[cat $(DESTDIR)/var/db/zoneinfo] as $(DESTDIR)/etc/localtime" tzsetup $tzs_args -r } else { echo "*** There is no $(DESTDIR)/var/db/zoneinfo file to update $(DESTDIR)/etc/localtime." echo ' You should run tzsetup' run_it_now "tzsetup $tzs_args" } } echo '' if test -r $(MM_EXIT_SCRIPT) { source "${MM_EXIT_SCRIPT}" } match $(COMP_CONFS) { with '' with * source ${DESTDIR}/etc/defaults/rc.conf shell {echo '' echo "*** Comparing conf files: $(rc_conf_files)" for CONF_FILE in [$(rc_conf_files)] { if test -r "$(DESTDIR)$(CONF_FILE)" { echo '' echo "*** From $(DESTDIR)$(CONF_FILE)" echo "*** From $(DESTDIR)/etc/defaults/rc.conf" for RC_CONF_VAR in [$[grep -i ^[a-z] $(DESTDIR)$(CONF_FILE) | cut -d '=' -f 1]] { echo '' grep -w ^$(RC_CONF_VAR) $(DESTDIR)$(CONF_FILE) grep -w ^$(RC_CONF_VAR) $(DESTDIR)/etc/defaults/rc.conf || echo ' * No default variable with this name' } } }} | $(PAGER) echo '' } if test -n $(PRESERVE_FILES) { find -d $PRESERVE_FILES_DIR -type d -empty -delete !2 >/dev/null rmdir $PRESERVE_FILES_DIR !2 >/dev/null } exit 0 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Copyright (c) 1998-2012 Douglas Barton # All rights reserved. # # 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 AUTHOR 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 AUTHOR 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.