#! /bin/sh
# Script to ask user if they want patches applied.
# Sun 12 Nov 2000 Harald Welte <laforge@gnumonks.org>
#		- added support for other protocols than ipv4

# set -x

printheader()
{
    clear 2> /dev/null
    echo "Welcome to Rusty's Patch-o-matic!"
    echo
    echo "Each patch is a new feature: many have minimal impact, some do not."
    echo "Almost every one has bugs, so I don't recommend applying them all!"
    echo "-------------------------------------------------------"
    if [ -n "$1" ]; then
	rest=${1# }
	echo -n "Already applied: "
	first=${rest%% *}
	rest=${1##*$first }
	echo $first
	for x in $rest
	do
	    echo "                 $x"
	done
	echo
    fi 
}

#
# WARNING: this function could fail, always check the output.
#
tmpdirname()
{
	dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -x -w32 -A n | tr -d ' '
}

# Too many rejects from trying to patch Configure.help and Config.in.
# So we use special format: First line specifies entry we want to
# follow, and rest of file is pasted in under that.

# Args: config.in file, netfilter dir.
apply_config_in_change()
{
    PRIOR="`head -1 $1`"
    LINE=`fgrep -x -n "$PRIOR" $2/Config.in | cut -d: -f1`
    if [ -z "$LINE" ]
    then
	echo Could not find place to slot in Config.in line >&2
	return 1
    fi
    rm -f $2/Config.in.tmp
    if (head -$LINE $2/Config.in && tail +2 $1 && tail +`expr $LINE + 1` $2/Config.in) > $2/Config.in.tmp
    then
	mv $2/Config.in.tmp $2/Config.in
    else
	echo Could not slot in Config.in line >&2
	rm -f $2/Config.in.tmp
	return 1
    fi
    echo "   Placed new Config.in line"
    return 0
}

# Args: configure.help file, Documentation dir.
apply_config_help_change()
{
    PRIOR="`head -1 $1`"
    LINE=`fgrep -x -n "$PRIOR" $2/Configure.help | cut -d: -f1 | head -1`
    if [ -z "$LINE" ] || [ "$LINE" -eq 0 ]
    then
	echo Could not find place to slot in Configure.help entry >&2
	return 1
    fi
    # Find *next* entry by char at beginning of line.
    NEXTOFFSET=`tail +$LINE $2/Configure.help | grep -n '^[A-Za-z0-9]' | head -1 | cut -d: -f1`
    if [ -z "$NEXTOFFSET" ] || [ "$NEXTOFFSET" -eq 0 ]
    then
	echo Could not find next place to slot in Configure.help entry >&2
	return 1
    fi
    LINE=`expr $LINE + $NEXTOFFSET - 3`

    rm -f $2/Configure.help.tmp
    if (head -$LINE $2/Configure.help && tail +2 $1 && echo && tail +`expr $LINE + 1` $2/Configure.help) > $2/Configure.help.tmp
    then
	mv $2/Configure.help.tmp $2/Configure.help
    else
	echo Could not slot in Configure.help entry >&2
	rm -f $2/Configure.help.tmp
	return 1
    fi
    echo "   Placed new Configure.help entry"
    return 0
}

# Args: makefile file, netfilter dir.
apply_makefile_change()
{
    PRIOR="`head -1 $1`"
    END="`tail +2 $1 | head -1`"
    LINE=`fgrep -x -n "$PRIOR" $2/Makefile | cut -d: -f1`
    if [ -z "$LINE" ] || [ "$LINE" -eq 0 ]
    then
	echo Could not find place to slot in Makefile entry >&2
	return 1
    fi
    
    rm -f $2/Makefile.tmp
    if (head -$LINE $2/Makefile && tail +2 $1 && tail +`expr $LINE + 1` $2/Makefile) > $2/Makefile.tmp
    then
	mv $2/Makefile.tmp $2/Makefile
    else
	echo Could not slot in Makefile entry >&2
	rm -f $2/Makefile.tmp
	return 1
    fi
    echo "   Placed new Makefile entry $1"
    return 0
}

# Args: ip{6}_conntrack.h "patch" file, netfilter_ipv{4|6} include dir,
#       ip{6}_conntrack.h filename.
apply_conntrack_h_change()
{
    PRIOR="`head -1 $1`"
    END="`tail +2 $1 | head -1`"
    LINE=`fgrep -x -n "$PRIOR" $2/$3 | cut -d: -f1`
    if [ -z "$LINE" ] || [ "$LINE" -eq 0 ]
    then
	echo Could not find place to slot in $3 entry >&2
	return 1
    fi
    
    rm -f $2/${3}.tmp
    if (head -$LINE $2/$3 && tail +2 $1 && tail +`expr $LINE + 1` $2/$3) > $2/${3}.tmp
    then
	mv $2/${3}.tmp $2/$3
    else
	echo Could not slot in $3 entry >&2
	rm -f $2/${3}.tmp
	return 1
    fi
    echo "   Placed new $3 entry $1"
    return 0
}

# Don't like to use GLOBIGNORE stuff; can't use shopt (bash v1).
expand_no_backups()
{
    for expansion in $1
    do
	case "$expansion"
	in
	    "$1") ;;
	    *~) ;;
	    *) echo "$expansion";;
	esac
    done
}

apply_config_in_changes()
{
	ret=0

	for x in `expand_no_backups "$1.config.in*"`
	do
	    apply_config_in_change $x $2 || ret=1
	done

	return $ret
}

apply_config_help_changes()
{
	ret=0

	for x in `expand_no_backups "$1.configure.help*"`
	do
	    apply_config_help_change $x $2 || ret=1
	done

	return $ret
}

apply_makefile_changes()
{
	ret=0

	for x in `expand_no_backups "$1.makefile*"`
	do
	    apply_makefile_change $x $2 || ret=1
	done

	return $ret
}

apply_conntrack_h_changes()
{
	ret=0
	case $2 in
		*ipv4)
			conntrack_h=ip_conntrack.h
			;;
		*ipv6)
			conntrack_h=ip6_conntrack.h
			;;
		*)
			return $ret
			;;
	esac

	for x in `expand_no_backups "$1.${conntrack_h}*"`
	do
	    apply_conntrack_h_change $x $2 $conntrack_h || ret=1
	done

	return $ret
}

# I'm paranoid.  Test patch first.
# Args: patch filename, protocol.
test_patch()
{
    KTMPDIR=`tmpdirname`
    # I'm really paranoid.  What if there's no /dev/urandom?
    if test -z "$KTMPDIR"; then
        echo Failed to generate tmpdirname: perhaps your /dev/urandom is broken >&2
	exit 1
    fi
    KTMPDIR=$KERNEL_DIR/../$KTMPDIR
    # On exit, clean up
    trap "rm -rf $KTMPDIR" 0
    if cp -al $KERNEL_DIR/. $KTMPDIR
    then :
    else
	echo Failed to make copy of $KERNEL_DIR >&2
	rm -rf $KTMPDIR
	exit 1
    fi

    echo Testing patch $1...

    if apply_config_in_changes $1 $KTMPDIR/net/$2/netfilter &&
	apply_config_help_changes $1 $KTMPDIR/Documentation &&
	apply_makefile_changes $1 $KTMPDIR/net/$2/netfilter &&
	apply_conntrack_h_changes $1 $KTMPDIR/include/linux/netfilter_$2
    then :
    else
	rm -rf $KTMPDIR
	return 1
    fi

    REJECTSBEFORE="`find $KTMPDIR/. -name '*.rej' -exec cat {} \; | grep -c '^\*\*\* '`"
    if (cd $KTMPDIR && patch -p1 >/dev/null) < $1
    then :
    else
	echo Failed to patch copy of $KERNEL_DIR >&2
	rm -rf $KTMPDIR
	return 1
    fi
    REJECTSAFTER="`find $KTMPDIR/. -name '*.rej' -exec cat {} \; | grep -c '^\*\*\* '`"
	
    if [ "$REJECTSBEFORE" -ne "$REJECTSAFTER" ]
    then
	echo Patch $1 seems to have had rejects \(`expr $REJECTSAFTER - $REJECTSBEFORE` new rejections\) >&2
	rm -rf $KTMPDIR
	return 1
    fi
    rm -rf $KTMPDIR

    echo "   Patch $1 applied cleanly."
    return 0
}

# Args: patch filename, protocol.
apply_patch()
{
    echo Applying patch $1...
    REJECTSBEFORE="`find $KERNEL_DIR/. -name '*.rej' -exec cat {} \; | grep -c '^\*\*\* '`"
    if (cd $KERNEL_DIR && patch -p1 > /dev/null) < $1
    then :
    else
	echo Failed to patch $KERNEL_DIR >&2
	exit 1
    fi
    REJECTSAFTER="`find $KERNEL_DIR/. -name '*.rej' -exec cat {} \; | grep -c '^\*\*\* '`"

    if [ $REJECTSBEFORE -ne $REJECTSAFTER ]
    then
	echo WARNING: patch $1 seems to have had rejects \(`expr $REJECTSAFTER - $REJECTSBEFORE` new rejections\):
	find $KERNEL_DIR/. -name '*rej'
    else
	echo "   Patch $1 applied cleanly."
    fi
    apply_config_in_changes $1 $KERNEL_DIR/net/$2/netfilter/
    apply_config_help_changes $1 $KERNEL_DIR/Documentation/
    apply_makefile_changes $1 $KERNEL_DIR/net/$2/netfilter/
    apply_conntrack_h_changes $1 $KERNEL_DIR/include/linux/netfilter_$2
}

# Make sure we are in the correct directory
if [ ! -f ../isapplied ]
then
    echo
    echo Please call $0 from the patch-o-matic directory!
    exit 1
fi 

# Script arg: suite name or a single patch file
if [ -d "$1" -a -e $1/SUITE ]
then
    for x in `cat $1/SUITE`
    do
	PATCHES="$PATCHES `ls $x/*.patch $x/*.patch.ipv6 2>/dev/null`"
    done
elif [ -e "$1" -a -n "${1%%.patch*}" ]
then
	PATCHES=$1
else
    echo
    echo "Usage: $0 suite|suite/patch-file"
    echo
    echo Possible patch-o-matic suites:
    echo
    for x in */SUITE; do
	suite=${x%%/*}
	echo ${suite}:
	fold -s -w 56 $suite/DESCRIPTION | sed 's/^/    /'
    done
    exit 1
fi

if [ -z "$KERNEL_DIR" ]
then
    echo Hey\! KERNEL_DIR is not set.
    echo -n "Where is your kernel? [/usr/src/linux] "
    read KERNEL_DIR
fi

if [ ! -f ${KERNEL_DIR:=/usr/src/linux}/Makefile ]
then
    echo $KERNEL_DIR doesn\'t look like a kernel tree to me. >&2
    exit 1
fi
VERSION=`grep '^VERSION' $KERNEL_DIR/Makefile | cut -d= -f2`
PATCHLEVEL=`grep '^PATCHLEVEL' $KERNEL_DIR/Makefile | cut -d= -f2`
if [ "$VERSION" -lt 2 -o "$PATCHLEVEL" -lt 4 ]
then
    echo $KERNEL_DIR looks like a $VERSION.$PATCHLEVEL kernel tree to me. >&2
    echo I expect a 2.4 kernel or above. >&2
    exit 1
fi

echo "Examining kernel in $KERNEL_DIR"
echo "-----------------------------------------------------------------"

APPLIED=""
SEEN=""
for f in $PATCHES
do
    SUITE=${f%%/*}
    FILE=${f##$SUITE/}
    BASE=${FILE%%.patch*}	# filename without .patch*
    PROTO=${FILE##$BASE.patch}
    PROTO=${PROTO##.}
    printheader "$APPLIED"
    if echo $SEEN | grep $BASE${PROTO:+-$PROTO}
    then
	# Patches seen at previous suites are skipped
	:
    else
    	SEEN="$SEEN $SUITE/$BASE${PROTO:+-$PROTO}"
    	echo -n "Testing... "
    	if ../isapplied $KERNEL_DIR $f
    	then
	    APPLIED="$APPLIED $SUITE/$BASE${PROTO:+-$PROTO}"
    	else
	    echo The $SUITE/$BASE ${PROTO:+$PROTO } patch:
	    while read LINE; do echo "   $LINE"; done < $f.help
	    echo
	    ANSWER=""
	    while [ "$ANSWER" = "" ]
	    do
	    	echo -n 'Do you want to apply this patch [N/y/t/f/q/?] '
	    	read ANSWER
	    	case "$ANSWER" in 
	    	y*|Y*)
		    if test_patch $f ${PROTO:-"ipv4"}
		    then
		    	apply_patch $f ${PROTO:-"ipv4"}
		    	APPLIED="$APPLIED $SUITE/$BASE${PROTO:+-$PROTO}"
		    else
		    	echo TEST FAILED: patch NOT applied.
		    	ANSWER=""
		    fi
		    ;;
	    	t*|T*)
		    ANSWER=""
		    test_patch $f ${PROTO:-"ipv4"}
		    ;;
	    	f*|F*)
		    apply_patch $f ${PROTO:-"ipv4"}
		    APPLIED="$APPLIED $SUITE/$BASE${PROTO:+-$PROTO}"
		    ;;
	    	N*|n*|'')
		    ANSWER=N ;;
	    	q*|Q*)
		    echo Bye!
		    exit 0 ;;
	    	*)
		    ANSWER=""
		    echo "Answer one of the following: "
		    echo "  T to test that patch will apply cleanly"
		    echo "  Y to apply patch"
		    echo "  N to skip this patch"
		    echo "  F to apply patch even if test fails"
		    echo "  Q to quit immediately"
		    echo "  ? for help"
		    echo
		    ;;
	    	esac
		echo "-----------------------------------------------------------------"
	    done
    	fi
    fi
done

echo
echo Excellent\!  Kernel is now ready for compilation.
exit 0
