#!/bin/bash
#
# Copyright (c) 2009 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 3 (GPLv3). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv3
# along with this software; if not, see
# http://www.gnu.org/licenses/gpl-3.0.txt.
#
# Red Hat trademarks are not licensed under GPLv3. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#

DEBUG=''
#uncomment this line if you do not want really remove your files.
#DEBUG="echo DEBUG:"

SELINUX=''

MERGE_TYPE='None'

print_help () {
        cat <<HELP
usage: rpmconf [options]

options:
        -a, --all
                Check configuration files of all packages.
        -opackage, --owner=package
                Check only configuration files of given package.
        -ftype, --frontend=type
                Define which frontend should be used for merging.
                For list of valid types see man page.
        -c, --clean
                Find and delete orphaned .rpmnew and .rpmsave files.
        -d, --debug
                Dry run. Just show which file will be deleted.
        -V, --version
		Display rpmconf version.
        -Z
                Display SELinux context of old and new file.
HELP
        exit
}

function toUpper {
  echo $1 | tr "[:lower:]" "[:upper:]" 
} 

function push_to_PACKAGE {
        PACKAGE[${#PACKAGE[@]}]=$1
}

function validate_merge_type {
	which $1 &>/dev/null || (
		echo "Error: $1 is not installed"
		exit 1
	)
}
function merge_conf_files {
	CONF_FILE=$1
        OTHER_FILE=$2

	case $MERGE_TYPE in
		# vimdiff, gvimdiff, meld return 0 even if file was not saved
		# we may handle it some way. check last modification? ask user?
		vimdiff) vimdiff "$CONF_FILE" "$OTHER_FILE"; $DEBUG rm "$OTHER_FILE";;
		gvimdiff) gvimdiff "$CONF_FILE" "$OTHER_FILE"; $DEBUG rm "$OTHER_FILE";;
                diffuse) diffuse "$CONF_FILE" "$OTHER_FILE"; $DEBUG rm "$OTHER_FILE";;
		kdiff3) kdiff3 "$CONF_FILE" "$OTHER_FILE" -o "$CONF_FILE" && $DEBUG rm "$OTHER_FILE" "${CONF_FILE}.orig";;
		meld) meld "$CONF_FILE" "$OTHER_FILE"; $DEBUG rm "$OTHER_FILE";;
		None) echo Error: you did not selected any frontend for merge.; exit 2;;
	        *) echo Error: Invalid option $1; exit 2;;
	esac

}

function handle_rpmnew {
        CONF_FILE=$1
        OTHER_FILE=$2
        if diff "$CONF_FILE" "$OTHER_FILE" &>/dev/null; then 
                $DEBUG rm "$OTHER_FILE"
                return
        fi
        OPTION=""
        while [ "$OPTION" != "Y" -a "$OPTION" != "I" -a "$OPTION" != "N" -a "$OPTION" != "O" -a "$OPTION" != "M" -a "$OPTION" != "S" ]; do
		echo "Configuration file \`$CONF_FILE'"
		ls -ltrd $SELINUX "$CONF_FILE" "$OTHER_FILE"
                cat <<ASK
 ==> Package distributor has shipped an updated version.
   What would you like to do about it ?  Your options are:
    Y or I  : install the package maintainer's version
    N or O  : keep your currently-installed version
      D     : show the differences between the versions
      M	    : merge configuration files
      Z     : background this process to examine the situation
      S     : skip this file
 The default action is to keep your current version.
*** aliases (Y/I/N/O/D/Z/S) [default=N] ? 
ASK
                read -u 1 -p "Your choice: " OPTION
                RC=$?
                if [ $RC -ne 0 ]; then
                        OPTION="S"
                elif [ "$OPTION" = "" ]; then
                        OPTION="N"
                fi
		OPTION=`toUpper "$OPTION"`
                if [ "$OPTION" = "D" ]; then
                        diff -u "$CONF_FILE" "$OTHER_FILE" |less
                elif [ "$OPTION" = "Z" ]; then
			kill -STOP $$
		fi
        done
        if [ "$OPTION" = "N" -o "$OPTION" = "O" ]; then
                $DEBUG rm "$OTHER_FILE"
        elif [ "$OPTION" = "Y" -o "$OPTION" = "I" ]; then 
                $DEBUG cp "$OTHER_FILE" "$CONF_FILE" \
                    && $DEBUG rm "$OTHER_FILE"
	elif [ "$OPTION" = "M" ]; then
                merge_conf_files "$CONFFILE" "$OTHER_FILE"
        fi
}

function handle_rpmsave {
        CONF_FILE=$1
        OTHER_FILE=$2
        if diff "$CONF_FILE" "$OTHER_FILE" &>/dev/null; then
                $DEBUG rm "$OTHER_FILE"
                return
        fi
        OPTION=""
        while [ "$OPTION" != "Y" -a "$OPTION" != "I" -a "$OPTION" != "N" -a "$OPTION" != "O" -a "$OPTION" != "M" -a "$OPTION" != "S" ]; do
		echo "Configuration file \`$CONF_FILE'"
		ls -ltrd $SELINUX "$CONF_FILE" "$OTHER_FILE"
                cat <<ASK
 ==> Package distributor has shipped an updated version.
 ==> Maintainer forced upgrade. Your old version has been backed up.
   What would you like to do about it ?  Your options are:
    Y or I  : install (keep) the package maintainer's version
    N or O  : return back to your original file
      D     : show the differences between the versions
      M     : merge configuration files
      Z     : background this process to examine the situation
      S     : skip this file
 The default action is to keep package maintainer's version.
*** aliases (Y/I/N/O/D/Z/S) [default=Y] ?
ASK
                read -u 1 -p "Your choice: " OPTION
                RC=$?
                if [ $RC -ne 0 ]; then
                        OPTION="S"
                elif [ "$OPTION" = "" ]; then
                        OPTION="Y"
                fi
		OPTION=`toUpper "$OPTION"`
                if [ "$OPTION" = "D" ]; then
                        diff -u "$OTHER_FILE" "$CONF_FILE" |less
		elif [ "$OPTION" = "Z" ]; then
                        kill -STOP $$
		elif [ "$OPTION" = "M" ]; then
			merge_conf_files "$CONFFILE" "$OTHER_FILE"
                fi
        done
        if [ "$OPTION" = "N" -o "$OPTION" = "O" ]; then
                $DEBUG cp "$OTHER_FILE" "$CONF_FILE" \
                    && $DEBUG rm "$OTHER_FILE"
        elif [ "$OPTION" = "Y" -o "$OPTION" = "I" ]; then
                $DEBUG rm "$OTHER_FILE"
	elif [ "$OPTION" = "M" ]; then
                merge_conf_files "$CONFFILE" "$OTHER_FILE"
        fi
}

function handle_packages {
        PACKAGE=$1
        for CONFFILE in `LANG= rpm -qc $PACKAGE |grep -v '(contains no files)'`; do
                if [ -f "$CONFFILE.rpmnew" ]; then
                        handle_rpmnew "$CONFFILE" "$CONFFILE.rpmnew"
                fi
                if [ -f $CONFFILE.rpmsave ]; then
                        handle_rpmsave "$CONFFILE" "$CONFFILE.rpmsave"
                fi
                if [ -f $CONFFILE.rpmorig ]; then
                        handle_rpmsave "$CONFFILE" "$CONFFILE.rpmorig"
                fi
        done
}

function configure_packages {
        PACKAGE=$1
        if [ "$PACKAGE" = "-a" ]; then
                # this is faster then getting list of all packages and looping through
                for FILE in /usr/share/rpmconf/*; do
                        rpm -qf $FILE >/dev/null 2>/dev/null && $FILE
                done
        elif [ -x /usr/share/rpmconf/$PACKAGE ]; then
                /usr/share/rpmconf/$PACKAGE
        fi
}

function clean_orphan {
        FILES_MERGE=$(mktemp --tmpdir --suffix=.merge rpmconf.XXXXXXXXXX)
        FILES_DELETE=$(mktemp --tmpdir --suffix=.delete rpmconf.XXXXXXXXXX)
        find /etc /var /usr -name '*.rpmnew' -o -name '*.rpmsave' | \
        while read rpmnew_rpmsave; do
            rpmnew_rpmsave_orig=$(echo $rpmnew_rpmsave | sed 's/\(\.rpmnew\)\|\(\.rpmsave\)//g')
            if rpm -qf $rpmnew_rpmsave_orig >/dev/null 2>&1; then
                echo -e $(rpm -qf $rpmnew_rpmsave_orig --qf '%{name}') "\t\t$rpmnew_rpmsave" >> $FILES_MERGE
            else
                echo $rpmnew_rpmsave >> $FILES_DELETE
            fi
        done
        if [ -s $FILES_MERGE ]; then
            echo "These files need merging - you may want to run 'rpmconf -a':"
            cat $FILES_MERGE
            echo "Skipping files above."
            echo
            rm -f $FILES_MERGE
        fi
        if [ -s $FILES_DELETE ]; then
            echo "Orphaned .rpmnew and .rpmsave files:"
            cat $FILES_DELETE
            read -u 1 -p "Delete these files (Y/n): " OPTION
            RC=$?
            if [ $RC -eq 0 ]; then
                OPTION=$(toUpper "$OPTION")
                if [ "$OPTION" = "Y" -o "$OPTION" = "" ]; then
                    cat $FILES_DELETE | cut -f2 -d' ' | xargs $DEBUG rm -f 
                fi
                rm -f $FILES_DELETE
            fi
        fi
}

if [ $# -eq 0 ]; then
        print_help;
fi
unset PACKAGE
while [ $# -ge 1 ]; do
    case $1 in
            --help | -h)  print_help;;
            -o*) push_to_PACKAGE $(echo $1 | cut -c3-);;
            --owner=*) push_to_PACKAGE $(echo $1 | cut -d= -f2-);;
            --all | -a) push_to_PACKAGE '-a';;
            --debug | -d) DEBUG="echo DEBUG:";;
            --version | -V) rpm -q rpmconf; exit;;
            -f*) MERGE_TYPE=$(echo $1 | cut -c3-);;
            --frontend=*) MERGE_TYPE=$(echo $1 | cut -d= -f2-);;
            --clean | -c) clean_orphan;;
            -Z) SELINUX="--lcontext";;
            *) echo Error: Invalid option $1; exit;
    esac
    shift
done

case $MERGE_TYPE in
	None) ;;
	vimdiff) validate_merge_type vimdiff || exit;;
	gvimdiff) validate_merge_type gvimdiff || exit;;
        diffuse) validate_merge_type diffuse || exit;;
	kdiff3) validate_merge_type kdiff3 || exit;;
	meld) validate_merge_type meld || exit;;
	*) cat <<ERROR; exit;;
Error: Invalid frontend for merge: $MERGE_TYPE
See man page for valid list of frontends
ERROR
esac

for P in ${PACKAGE[@]}; do
        handle_packages $P
done

for P in ${PACKAGE[@]}; do
        configure_packages $P
done
