#! /bin/sh
# Script to ask user if they want patches applied or reversed.
# Sun 12 Nov 2000 Harald Welte <laforge@gnumonks.org>
#		- added support for other protocols than ipv4
# Tue 12 Feb 2002 Bob Hockney <zeus@ix.netcom.com>
#		- added support for reverting patches out
# Fri 22 Feb 2002 Fabrice MARIE <fabrice@netfilter.org>
#               - added support for userspace/ patches
# Sat 25 Jan 2003 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
#		- removed copying the source trees for testing
# Fri 21 Feb 2003 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
#		- added dependecy support
# Sat  5 Apr 2003 Martin Josefsson <gandalf@wlug.westbo.se>
#		- added support for conflicting dependencies
#		- readded partial support for sourcetree copying
#		- fix support for userspace/ patches
# Mon  7 Apr 2003 Martin Josefsson <gandalf@wlug.westbo.se>
#		- add support for batch testing
#

#set -x

printheader()
{
    clear 2> /dev/null
    echo "Welcome to Rusty's Patch-o-matic!"
    echo
    echo "Kernel:    $KERNEL_DIR"
    echo "Userspace: $NETFILTERDIR"
    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
	if [ $MODE ]; then
    	    echo -n "NOT applied:     "
	else
	    echo -n "Already applied: "
	fi
	rest=${1# }
	first=${rest%% *}
	rest=${1#*$first}
	echo $first
	for x in $rest
	do
	    echo "                 $x"
	done
	echo
    fi 
}

entertocont()
{
	echo ""
	echo "[Press enter to continue]"
	read enter
	echo
}

#
# 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 ' '
}

alloc_ktmpdir()
{
	if [ "$KTMPDIR" == "$KERNEL_DIR" ]; then
		KTMPDIR=`tmpdirname`
		# I'm really paranoid.  What if there's no /dev/urandom?
		if [ -z "$KTMPDIR" ]; then
		   echo Failed to generate kernel tmpdirname: perhaps your /dev/urandom is broken >&2
		   free_utmpdir
		   exit 1
		fi
		KTMPDIR=$KERNEL_DIR/../$KTMPDIR
		if cp -al $KERNEL_DIR/. $KTMPDIR
		then :
		else
		   echo Failed to make copy of $KERNEL_DIR >&2
		   free_ktmpdir
		   free_utmpdir
		   exit 1
		fi
	fi
}

free_ktmpdir()
{
	if [ "$KTMPDIR" != "$KERNEL_DIR" ]; then
		rm -rf $KTMPDIR
		KTMPDIR=$KERNEL_DIR
	fi
}

alloc_utmpdir()
{
	if [ "$UTMPDIR" == "$NETFILTERDIR" ]; then
		UTMPDIR=`tmpdirname`
		# I'm really paranoid.  What if there's no /dev/urandom?
		if [ -z "$UTMPDIR" ]; then
		   echo Failed to generate userspace tmpdirname: perhaps your /dev/urandom is broken >&2
		   free_ktmpdir
		   exit 1
		fi
		UTMPDIR=$NETFILTERDIR/../$UTMPDIR
		if cp -al $NETFILTERDIR/. $UTMPDIR
		then :
		else
		   echo Failed to make copy of $NETFILTERDIR >&2
		   free_utmpdir
		   free_ktmpdir
		   exit 1
		fi
	fi
}

free_utmpdir()
{
	if [ "$UTMPDIR" != "$NETFILTERDIR" ]; then
		rm -rf $UTMPDIR
		UTMPDIR=$NETFILTERDIR
	fi
}

# Too many rejects from trying to patch Configure.help/Config.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: "patch" file, directory, file to patch, test mode
apply_change()
{
    PRIOR="`head -1 $1`"
    LINE=`fgrep -x -n "$PRIOR" $2/$3 | cut -d: -f1 | head -1`
    if [ -z "$LINE" ]
    then
	echo Could not find place to slot in $3 line >&2
	return 1
    fi
    rm -f $2/${3}.tmp
 
    if [ $MODE ]; then
        # Reverse "patch"

        # Need to search for previously inserted lines 
        # Might not be immediately under $LINE
        BEGIN=2
        TESTLINE=$BEGIN
        NUMLINES1=`sed -n \$= $1`
        NUMLINES2=`sed -n \$= $2/$3`

        while [ $TESTLINE -le $NUMLINES1 ]; do
            NOMATCH=`awk "NR==$LINE + $TESTLINE - 1" $2/$3|\
                    fgrep -vcx "\`sed -n -e ${TESTLINE}p $1\`"`;
            if [ $NOMATCH -ne 0 ]; then
                LINE=`expr $LINE + $TESTLINE - 1`
                TESTLINE=$BEGIN
                continue
            fi;

            TESTLINE=`expr $TESTLINE + 1`;

        done; 

        if [ $LINE -lt $NUMLINES2 ] && \
            (awk "NR==1,NR==$LINE" $2/$3 && awk "NR==$LINE + $NUMLINES1,NR=0" $2/$3) > $2/${3}.tmp
        then
	    if [ "$4" = "test" ]
	    then
		rm -f $2/${3}.tmp
	    else
                mv $2/${3}.tmp $2/$3
	    fi
            echo "   $3 updated"
            return 0
        else
            echo Could not update $3 >&2
            rm -f $2/${3}.tmp
            return 1
        fi
    fi
    # Apply "patch"
    # Use awk to properly add newline if last line of code has only spaces
    # Necessary to properly remove inserted code if patch is reversed
    if (awk "NR==1,NR==$LINE" $2/$3 && awk "NR==2,NR==0" $1 && \
        awk "NR==$LINE+1,NR==0" $2/$3) > $2/${3}.tmp
    then
	if [ "$4" = "test" ]
	then
	    rm -f $2/${3}.tmp
	else
	    mv $2/${3}.tmp $2/$3
	fi
    else
	echo Could not slot in $3 line >&2
	rm -f $2/${3}.tmp
	return 1
    fi
    echo "   Placed new $3 line"
    return 0
}

# Args: configure.help file, Documentation file to which to add it.
apply_config_help_change()
{
    if [ "$2" = '/dev/null' ]; then
        #return 0	#Use this one if you don't care that the config*.help file is not getting updated
        return 1	#Fail
    fi
    BASEFILE=`basename $2`	#Configure.help or Config.help
    PRIOR="`head -1 $1`"
    LINE=`fgrep -x -n "$PRIOR" $2 | cut -d: -f1 | head -1`
    if [ -z "$LINE" ] || [ "$LINE" -eq 0 ]
    then
	echo Could not find place to slot in $BASEFILE entry >&2
	return 1
    fi
    rm -f $2.tmp

    if [ $MODE ];
    then
        # Reverse "patch"

        NUMLINES1=`sed -n \$= $1`
        NUMLINES2=`sed -n \$= $2`
        BEGIN=2

        LINES=`fgrep -nx "\`sed -n 2p $1\`" $2 | cut -d: -f1`
        if [ -z "$LINES" ]
        then
	    echo $BASEFILE text not found >&2
            return 1
        fi

        # First line of inserted text may appear more than once
        # Check each occurance
        MATCHES=
        for x in $LINES; do
            TESTLINE=$BEGIN
            # Be paranoid and check for match on all lines
            while `let "$TESTLINE <= $NUMLINES1"`; do
                NOMATCH=`awk "NR==$x + $TESTLINE - $BEGIN" $2|\
                        fgrep -xcv "\`sed -n -e ${TESTLINE}p $1\`"`;

                if [ $NOMATCH -ne 0 ]; then continue 2; fi

                TESTLINE=`expr $TESTLINE + 1`;

            done; # while
            MATCHES="$x $MATCHES"

        done; # for

        if [ ! "$MATCHES" ]; then
            echo $BASEFILE text not found >&2
            return 1
        fi;

        for x in $MATCHES; do
#            if (head -`expr $x - 1` $2 && \
#                tail -n +`expr $x + $NUMLINES1` $2) > $2.tmp
	    if (awk "NR==1,NR==$x - 1" $2 && \
                awk "NR==$x + $NUMLINES1,NR=0" $2) > $2.tmp
            then
		if [ "$3" = "test" ]
		then
		    rm -f $2.tmp
		else
	            mv $2.tmp $2
		fi
            else
                echo Could not update $BASEFILE >&2
                rm -f $2.tmp
                return 1
            fi
        done
        echo "   $BASEFILE updated"
        return 0
    fi

    #Apply "Patch"

    # Use awk to force newline if last line of $1 has only spaces
    # Necessary to properly remove inserted text if patch is reversed
    if (awk "NR==1,NR==$LINE-2" $2 && awk "NR==2,NR==0" $1 && echo && \
        awk "NR==$LINE-1,NR==0" $2) > $2.tmp
    then
	if [ "$3" = "test" ]
	then
	    rm -f $2.tmp
	else
	    mv $2.tmp $2
	fi
    else
	echo Could not slot in $BASEFILE entry >&2
	rm -f $2.tmp
	return 1
    fi
    echo "   Placed new $BASEFILE entry"
    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_change $x $2 Config.in $3 || 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 $3 || ret=1
	done

	return $ret
}

apply_makefile_changes()
{
	ret=0

	for x in `expand_no_backups "$1.makefile*"`
	do
	    apply_change $x $2 Makefile $3 || 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_change $x $2 $conntrack_h $3 || ret=1
	done

	return $ret
}

# Args: patch filename, protocol.
test_patch_depend()
{
    # Check the dependecy of the patch
    if [ -f "$1.depend" -a -z "$MODE" ]; then
	while read dep
	do
	    if [ -z "${dep###*}" ]; then
	    	continue
	    fi

	    not=0
	    if [ -z "${dep##not *}" ]; then
		dep=${dep##not }
		not=1
	    fi
	    
	    f=${dep%%.patch*}	# filename without .patch*
	    p=${dep##$f.patch}	# protocol, if exist
	    p=${p##.}
	    n=$f${p:+-$p}
	    if [ "`echo $PROCESSED | grep $n`" ]; then
		if [ $not == 1 ]; then
			echo >&2
			echo "Conflicting dependency." >&2
			echo "   $1" >&2
			echo "is incompatible with" >&2
			echo "   $dep" >&2
			echo >&2
			return 1
		else
			continue
		fi
	    elif [ "`echo "$DEPEND" | grep " $dep "`" ]; then
		echo >&2
		echo "Circular dependency, patch $1 cannot be applied." >&2
 		echo "Report the problem to the patch-o-matic maintainer." >&2
		echo >&2
		return 1
	    fi
	    if [ $not == 1 ]; then
		echo Patch $1 conflicts with $dep...
	    else
	    	echo Patch $1 depends on $dep...
	    fi
	    DEPEND="$DEPEND $dep "
            if ./isapplied $KTMPDIR $dep
	    then 
		if [ $not == 1 ]; then
			echo >&2
			echo "Conflicting dependency." >&2
			echo "   $1" >&2
			echo "is incompatible with" >&2
			echo "   $dep" >&2
			echo >&2
			return 1
		fi
            elif [ $not == 1 ] || apply_patch_depend $dep ${p:-"ipv4"} test
	    then :
	    else
		return 1
	    fi
	done < $1.depend
    fi

    echo Testing patch $1...

    if [ -f "$KTMPDIR/net/$2/netfilter/Config.help" ]; then
    	DOCUMENTATIONFILE="net/$2/netfilter/Config.help"
    elif [ -f "$KTMPDIR/Documentation/Configure.help" ]; then
        DOCUMENTATIONFILE="Documentation/Configure.help"
    else
        echo Warning - no help text file could be found in either >&2
        echo $KTMPDIR/net/$2/netfilter/Config.help >&2
        echo or $KTMPDIR/Documentation/Configure.help >&2
	DOCUMENTATIONFILE=/dev/null
    fi

    if apply_config_in_changes $1 $KTMPDIR/net/$2/netfilter test &&
	apply_config_help_changes $1 $KTMPDIR/$DOCUMENTATIONFILE test &&
	apply_makefile_changes $1 $KTMPDIR/net/$2/netfilter test &&
	apply_conntrack_h_changes $1 $KTMPDIR/include/linux/netfilter_$2 test
    then :
    else
	return 1
    fi

    if (cd $KTMPDIR &&
	if [ $MODE ]; then
		patch -R -p1 --dry-run
	else
		patch -p1 --dry-run
	fi > /dev/null) < $1
    then :
    else
	echo "Failed to patch copy of $KERNEL_DIR" >&2
	return 1
    fi

    if [ ! -e $1.userspace ];
    then
    	echo "   Patch $1 `modesense 2` cleanly."
	return 0
    fi

    if (cd $UTMPDIR &&
    	if [ $MODE ]; then
		patch -R -p1 --dry-run
	else
		patch -p1 --dry-run
	fi > /dev/null) < $1.userspace
    then :
    else
	echo Failed to patch copy of $NETFILTERDIR >&2 
	return 1
    fi  
							
    echo "   Patch $1 `modesense 2` cleanly."
    return 0
}

# I'm paranoid.  Test patch first.
# Args: patch filename, protocol.
test_patch()
{
    DEPEND=" $1 "

    test_patch_depend $1 $2
    ret=$?

    free_ktmpdir
    free_utmpdir

    return $ret
}

# Args: patch filename, protocol.
apply_patch_depend()
{
    # Are we testing?
    if [ "$3" == "test" ]; then
	alloc_ktmpdir
    fi
    # Check the dependecy of the patch
    if [ "$3" != "force" -a -f "$1.depend" -a -z "$MODE" ]; then
	while read dep
	do
	    if [ -z "${dep###*}" ]; then
	    	continue
	    fi

	    not=0
	    if [ -z "${dep##not *}" ]; then
		dep=${dep##not }
		not=1
	    fi

	    f=${dep%%.patch*}	# filename without .patch*
	    p=${dep##$f.patch}	# protocol, if exist
	    p=${p##.}
	    n=$f${p:+-$p}
	    if [ "`echo $PROCESSED | grep $n`" ]; then
		if [ $not == 1 ]; then
			echo >&2
			echo "Conflicting dependency." >&2
			echo "   $1" >&2
			echo "is incompatible with" >&2
			echo "   $dep" >&2
			echo >&2
			return 1
		else
			continue
		fi
	    elif [ "`echo "$DEPEND" | grep " $dep "`" ]; then
		echo >&2
		echo "Circular dependency, patch $1 cannot be applied." >&2
 		echo "Report the problem to the patch-o-matic maintainer." >&2
		echo >&2
		return 1
	    fi
	    if [ $not == 1 ]; then
		echo Patch $1 conflicts with $dep...
	    else
	    	echo Patch $1 depends on $dep...
	    fi
	    DEPEND="$DEPEND $dep "
            if ./isapplied $KTMPDIR $dep
	    then 
		if [ $not == 1 ]; then
			echo >&2
			echo "Conflicting dependency." >&2
			echo "   $1" >&2
			echo "is incompatible with" >&2
			echo "   $dep" >&2
			echo >&2
			return 1
		fi
	    elif [ $not == 1 ] || apply_patch_depend $dep ${p:-"ipv4"} $3
	    then :
	    else
		return 1
	    fi
	done < $1.depend
    fi

    echo `modesense 3` patch $1...

    if (cd $KTMPDIR &&
	if [ $MODE ]; then
		patch -R -p1
	else
		patch -p1
	fi > /dev/null) < $1
    then :
    else
	echo "Failed to patch copy of $KERNEL_DIR" >&2
	return 1
    fi

    if [ -f "$KTMPDIR/net/$2/netfilter/Config.help" ]; then
    	DOCUMENTATIONFILE="net/$2/netfilter/Config.help"
    elif [ -f "$KTMPDIR/Documentation/Configure.help" ]; then
        DOCUMENTATIONFILE="Documentation/Configure.help"
    else
        echo Warning - no help text file could be found in either >&2
        echo $KTMPDIR/net/$2/netfilter/Config.help >&2
        echo or $KTMPDIR/Documentation/Configure.help >&2
	DOCUMENTATIONFILE=/dev/null
    fi

    apply_config_in_changes $1 $KTMPDIR/net/$2/netfilter/
    apply_config_help_changes $1 $KTMPDIR/$DOCUMENTATIONFILE
    apply_makefile_changes $1 $KTMPDIR/net/$2/netfilter/
    apply_conntrack_h_changes $1 $KTMPDIR/include/linux/netfilter_$2

    if [ ! -e $1.userspace ];
    then
    	echo "   Patch $1 `modesense 2` cleanly."
	return 0
    fi

    # Are we testing?
    if [ "$3" == "test" ]; then
    	alloc_utmpdir
    fi

    if (cd $UTMPDIR &&
    	if [ $MODE ]; then
		patch -R -p1
	else
		patch -p1
	fi > /dev/null) < $1.userspace
    then :
    else
	echo Failed to patch copy of $NETFILTERDIR >&2 
	return 1
    fi  
							
    echo "   Patch $1 `modesense 2` cleanly."
    if [ "$UTMPDIR" == "$NETFILTERDIR" ]; then
	    echo "   *******************"
	    echo "   ***** WARNING ***** You have applied a userspace patch, so do not forget to recompile"
	    echo "   ******************* and re-install iptables."
	    echo "   *******************"
    fi

    return 0
}

# Args: patch filename, protocol.
apply_patch()
{
    DEPEND=" $1 "

    apply_patch_depend $1 $2 $3
}

# Reverse order of arguments.
reverse_args()
{
    NUMARGS=$#
    while [ $NUMARGS -gt 0 ]; do
        eval "echo -n \${$NUMARGS}' '"
        NUMARGS=`expr $NUMARGS - 1`
    done
}

modesense()
{
    case $1 in
        1)
            if [ $MODE ]; then echo REVERSE; else echo apply; fi;
            ;;
        2)
	    if [ $MODE ]; then echo REVERSED; else echo applied; fi;
            ;;
        3)
	    if [ $MODE ]; then echo REVERSING; else echo Applying; fi;
            ;;
    esac
}

parse_filename()
{
    SUITE=${1%%/*}
    FILE=${1##$SUITE/}
    BASE=${FILE%%.patch*}	# filename without .patch*
    PROTO=${FILE##$BASE.patch}
    PROTO=${PROTO##.}
}

# Args: list of patches to process
process_patches()
{
    CURRENT=0
    while [ $CURRENT -le $# ];
    do
        CURRENT=`expr $CURRENT + 1`
        if [ $CURRENT -gt $# ];
        then
    if [ $BATCH ] || [ $CHECKMODE ] || [ $TESTMODE ]; then
    	if [ $CHECKMODE ]
	then
	   printheader "$PROCESSED"
	   printheader "$PROCESSED" > runme.out-check
	fi
	return
    fi
    printheader "$PROCESSED"
    ANSWER=""
    while [ ! $ANSWER ]
    do
        echo "-----------------------------------------------------------------"
	echo -n "No more patches to `modesense 1`! Q to Quit or ? for options "'[Q/a/r/b/?] '
        read ANSWER
        case "$ANSWER" in 
            a*|A*)
                WALK=
                NEWMODE=
                if ! [ "$NEWMODE" = "$MODE" ]; then PATCHES=`reverse_args $PATCHES`; fi;
                MODE=$NEWMODE
                continue 3 
                ;;
            r*|R*)
                WALK=
                NEWMODE=UN
                if ! [ "$NEWMODE" = "$MODE" ]; then PATCHES=`reverse_args $PATCHES`; fi;
                MODE=$NEWMODE
                continue 3 
                ;;
            b*|B*)
                WALK=1
                BACK=1
                SEEN=${SEEN%%$SUITE/$BASE${PROTO:+-$PROTO} }
                PROCESSED=${PROCESSED%% $SUITE/$BASE${PROTO:+-$PROTO}}
                if [ $CURRENT -gt 1 ]; then
                    CURRENT=`expr $CURRENT - 2`
                else
                    CURRENT=0
                fi
                continue 2 
                ;;
    	    Q*|q*|'')
	        ANSWER=Q
                continue 2 
                ;;
    	    *)
	        ANSWER=""
	        echo "Answer one of the following: "
                echo "  A to restart patch-o-matic in apply mode"
                echo "  R to restart patch-o-matic in REVERSE mode"
                echo "  B to walk Back one patch in the list"
	        echo "  Q to quit immediately"
	        echo "  ? for help"
	        ;;
        esac
    done
        fi

        THIS_PATCH=`eval echo -n \\\${$CURRENT}` > /dev/null
        parse_filename $THIS_PATCH # > /dev/null

        printheader "$PROCESSED"
        # Don't skip SEEN patches in REVERSE mode
        if [ ! $MODE ] && [ ! $BACK ] && echo $SEEN | tr ' ' '\012' | grep -q "$BASE${PROTO:+-$PROTO}\$"
        then
	    # Patches seen at previous suites are skipped
            continue
        else
            if [ $MODE ] || ! echo $SEEN | tr ' ' '\012' | grep -q "$BASE${PROTO:+-$PROTO}\$"; then
                SEEN="$SEEN$SUITE/$BASE${PROTO:+-$PROTO} "
            elif [ $BACK ]; then
                if [ $CURRENT -gt 1 ]; then
                    parse_filename `eval echo -n \\\${\`expr $CURRENT - 1\`}` > /dev/null
                    SEEN=${SEEN%%$SUITE/$BASE${PROTO:+-$PROTO} }
                    PROCESSED=${PROCESSED%% $SUITE/$BASE${PROTO:+-$PROTO}}
                    CURRENT=`expr $CURRENT - 2`
                else
                    CURRENT=0
                fi
                continue
            fi

    	    echo -n "Testing... "

            APPLIED=
            if STATUS=`./isapplied $KERNEL_DIR $THIS_PATCH`; then APPLIED=Y ; fi
            if [ ! $WALK ] && ([ "$APPLIED" -a -z "$MODE" ] || [ ! "$APPLIED" -a -n "$MODE" ]); 
	    then
                PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
                continue
	    elif [ $CHECKMODE ]
	    then
	    	continue
            else
                WALK=
                BACK=
	        echo $STATUS
		echo The $SUITE/$BASE ${PROTO:+$PROTO } patch:
		if [ -f $THIS_PATCH.help ]; then while read LINE; do echo "   $LINE"; done < $THIS_PATCH.help; fi
		ANSWER=""
		if [ $BATCH ] || [ $TESTMODE ]
		then
		    if test_patch $THIS_PATCH ${PROTO:-"ipv4"}
		    then
			if [ -z "$TESTMODE" ]; then
				apply_patch $THIS_PATCH ${PROTO:-"ipv4"}
				PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
			fi
			ANSWER=Y
		    else
			echo TEST FAILED: patch NOT `modesense 2`.
			exit 1
		    fi
		fi
		while [ "$ANSWER" = "" ]
		do
		    echo "-----------------------------------------------------------------"
		    echo -n "Do you want to `modesense 1` this patch "'[N/y/t/f/a/r/b/w/v/q/?] '
		    read ANSWER
		    case "$ANSWER" in 
		        y*|Y*)
			    if test_patch $THIS_PATCH ${PROTO:-"ipv4"}
			    then
			        apply_patch $THIS_PATCH ${PROTO:-"ipv4"}
			        PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
			    else
			        echo TEST FAILED: patch NOT `modesense 2`.
			        ANSWER=""
			    fi
			    entertocont # pause before screen is cleared by printheader
			    ;;
		        t*|T*)
			    ANSWER=""
			    test_patch $THIS_PATCH ${PROTO:-"ipv4"}
			    ;;
		        f*|F*)
			    apply_patch $THIS_PATCH ${PROTO:-"ipv4"} force
			    PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
			    entertocont # pause before screen is cleared by printheader
			    ;;
		        a*|A*)
			    NEWMODE=
			    if ! [ "$NEWMODE" = "$MODE" ]; then PATCHES=`reverse_args $PATCHES`; fi;
			    MODE=$NEWMODE
			    continue 3 
			    ;;
		        r*|R*)
			    NEWMODE=UN
			    if ! [ "$NEWMODE" = "$MODE" ]; then PATCHES=`reverse_args $PATCHES`; fi;
			    MODE=$NEWMODE
			    continue 3 
			    ;;
		        b*|B*)
			    WALK=1
			    BACK=1
		 	    SEEN=${SEEN%%$SUITE/$BASE${PROTO:+-$PROTO} }
			    PROCESSED=${PROCESSED%% $SUITE/$BASE${PROTO:+-$PROTO}}
			    if [ $CURRENT -gt 1 ]; then
			        parse_filename `eval echo -n \\\${\`expr $CURRENT - 1\`}` > /dev/null
			        SEEN=${SEEN%%$SUITE/$BASE${PROTO:+-$PROTO} }
			        PROCESSED=${PROCESSED%% $SUITE/$BASE${PROTO:+-$PROTO}}
			        CURRENT=`expr $CURRENT - 2`
		            else
			        CURRENT=0
		            fi
		            continue 2 
		            ;;
		        w*|W*)
		            WALK=1
		            if [ "$APPLIED" -a -z "$MODE" ] || [ ! "$APPLIED" -a -n "$MODE" ]; 
		            then
			        PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
		            fi
		            continue 2
		            ;;
		        N*|n*|'')
		            ANSWER=N
			        if [ "$APPLIED" -a -z "$MODE" ] || [ ! "$APPLIED" -a -n "$MODE" ]; 
		            then
			        PROCESSED="$PROCESSED $SUITE/$BASE${PROTO:+-$PROTO}"
 		            fi
 		            ;;
 		        q*|Q*)
		            echo Bye!
		            exit 0 ;;
			*)
		            ANSWER=""
		            echo "Answer one of the following: "
		            echo "  T to test that patch will `modesense 1` cleanly"
		            echo "  Y to `modesense 1` patch"
		            echo "  N to skip this patch"
		            echo "  F to `modesense 1` patch even if test fails"
		            echo "  A to restart patch-o-matic in apply mode"
		            echo "  R to restart patch-o-matic in REVERSE mode"
		            echo "  B to walk Back one patch in the list"
		            echo "  W to Walk forward one patch in the list"
		            echo "  Q to quit immediately"
		            echo "  ? for help"
		            ;;
 	            esac
 	        done # while [ "$ANSWER" = "" ]
            fi
        fi
    done # [ $CURRENT -le $# ] 
}

# 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 

if [ -z "$NETFILTERDIR" ]
then
	if [ -d ../userspace ]
	then
		export NETFILTERDIR=`cd ../userspace; pwd`
	else
		export NETFILTERDIR=`cd ..; pwd`
	fi
fi

UTMPDIR=$NETFILTERDIR

# Make sure we have 'patch'...
if patch -v >/dev/null 2>&1
then :
else
   echo program \'patch\' is missing, please install it!
   exit 1
fi

# Check to see if we are running in batch mode
BATCH=
if [ "$1" = "--batch" ]; then
    BATCH=1
    shift
fi

TESTMODE=
if [ "$1" = "--test" ]; then
    TESTMODE=1
    shift
fi

CHECKMODE=
if [ "$1" = "--check" ]; then
    CHECKMODE=1
    shift
fi

# Check to see if we are applying or reversing patches
MODE=
if [ "$1" = "--reverse" ]; then
    shift
    MODE=UN
fi

# Check to see if there is excluded patches
EXCLUDED=
while [ "$1" = "--exclude" ]; do
    EXCLUDED="$EXCLUDED $2"
    shift
    shift
done

# Script arg: suite name or a single patch file
PATCHES=""
for arg; do
if [ -d "$arg" -a -e $arg/SUITE ]
then
    for x in `cat $arg/SUITE`
    do
	PATCHES="$PATCHES `ls $x/*.patch $x/*.patch.ipv6 2>/dev/null`"
    done
elif [ -e "$arg" -a -n "${arg%%.patch*}" ]
then
	PATCHES="$PATCHES $arg"
else
    echo "ERROR: Invalid option $arg"
    exit 1
fi
done

if [ -z "$PATCHES" ]; then
    echo
    echo "Usage: $0 [--batch] [--reverse] [--exclude suite/patch-file ...] suite|suite/patch-file"
    echo
    echo "  --batch	batch mode, automatically applying patches"
    echo "  --test	test mode, automatically test patches"
    echo "  --check	check mode, automatically checks if patches are already applied"
    echo "		produces a logfile: runme.out-check"
    echo "  --reverse	back out the selected patches"
    echo "  --exclude	excludes the named patches"
    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 [ $MODE ]; then
    PATCHES=`reverse_args $PATCHES`
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
    if (cd $KERNEL_DIR )
    then :
    else
	echo "Failed to change directory to the kernel source dir $KERNEL_DIR" >&2
	exit 1
    fi
fi

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

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

KTMPDIR=$KERNEL_DIR

DONE=
until [ $DONE ];
do
    PROCESSED=
    SEEN="$EXCLUDED "
    process_patches $PATCHES
    DONE=1
done 

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