#!/bin/sh
#
# Patch mtd into kernel
#
# usage:patch [-j] kernelpath
# 	kernelpath must be given
#	-j includes filesystems (jffs, jffs2)
#	
# Works for Kernels >= 2.4.11 full functional
# Works for Kernels >= 2.4 and <= 2.4.10 partly (JFFS2 support is missing)
# For 2.2 Kernels it's actually disabled, as I have none to test it.
#
# You can use it for pristine kernels and for already patches kernels too.
#
# Detects Kernelversion and applies neccecary modifications
# For Kernelversions < 2.4.20 ZLIB-Patch is applied, if 
# filesystem option is set and ZLIB-Patch is not already there 
#
# Maybe some sed/awk experts would make it better, but I'm not
# one of them. Feel free to make it better
# 
# Thomas (tglx@linutronix.de)
#
# $Id: patchin.sh,v 1.37 2004/12/17 17:54:05 dedekind Exp $
#
# 09-12-2004 dedekind add JFFS3 installing capability, reformat, clean-up
# 24-05-2004 havasi Patch fs/Kconfig
# 05-05-2004 tglx Include include/mtd
# 12-06-2003 dwmw2 Leave out JFFS1, do Makefile.common only if it exists.
# 27-05-2003 dwmw2 Link Makefile to Makefile.common since we moved them around
# 02-10-2003 tglx replaced grep -m by head -n 1, as older grep versions don't support -m	
# 03-08-2003 tglx -c option for copying files to kernel tree instead of linking
#		  moved file selection to variables


# 
# Finds the first and last lines numbers of block of Kconfig file entries
# containing pattern $2. $1 contains the file name where to search for.
# Helper function for patch_Kconfig();
#
# Returns the result in FIRSTLINE and LASTLINE variables.
#
function find_lines () {
	# find the line number of the first and the last $2 entries
	FIRSTLINE=`grep -m1 -n $2 $1 | sed -e 's/:.*//'`;
	if [ "x$FIRSTLINE" = "x" ]; then
		FIRSTLINE=0;
		LASTLINE=0;
		return 0;
	fi;
	LASTLINE=`grep -n $2 $1 | sed -n -e '$s/:.*// p'`;
	let LASTLINE=$LASTLINE+1;
	LASTLINEADD=`sed -n $1 -e "$LASTLINE,$ p" | sed -n -e '/^\(choice\)\|\(config\)\|\(menu\)\s*/{=; q}'`;
	if [ "x$LASTLINEADD" = "x" ]; then
		let LASTLINEADD=`cat $1 | wc -l`-$LASTLINE+2;
	fi;
	let LASTLINE=$LASTLINE+$LASTLINEADD-2;
}

#
# Patch the Kconfig file. Helper function for patch_Kconfig().
# 
# Function requires the following positional parameters:
# 
# $1:	the path of the input Kconfig file from which the function
#	should fetch new the Kconfig entries;
# $2:	the path of the target linux Kconfig file which should be
#	patched; if the parameter value is '', just insert to the end of
#	the target Kconfig file;
# $3:	the line number of the first entry in the in the input file;
# $4:	the line number of the last line in the input file;
# $5:	the menu name in target Kconfig file where the new entries should
#	be added.
#
function do_patch_Kconfig () {
	local INFILE=$1;
	local OUTFILE=$2;
	local FIRSTLINE=$3;
	local LASTLINE=$4;
	local MENUNAME=$5;

	# read the entries from the input file
	local ENTRIES=`sed -n "$INFILE" -e "$FIRSTLINE,$LASTLINE p"`;

	# determine the menu line number
	local MENULINE='';
	local TOTALOUT=`cat $OUTFILE | wc -l`;
	if [ "x$MENUNAME" != "x" ]; then
		MENULINE=`grep menu $OUTFILE | grep -m1 -n "$MENUNAME" $OUTFILE | sed -e 's/:.*//'`;
	else
		let MENULINE=$TOTALOUT;
	fi;
	
	# copy lines 1-MENULINE from the target file to the temporary file
	TMPFILE="Kconfig.$$";
	sed -n "$OUTFILE" -e "1,$MENULINE p" > $TMPFILE;
	echo >> $TMPFILE;
	# append our lines
	sed -n "$INFILE" -e "$FIRSTLINE,$LASTLINE p" >> $TMPFILE;
	# append lines MENULINE+1-TOTALOUT from the target files to the temporary file
	if [ $MENULINE -ne $TOTALOUT ]; then
		let MENULINE=$MENULINE+1;
		sed -n "$OUTFILE" -e "$MENULINE,$TOTALOUT p" >> $TMPFILE;
	fi;
	# replace the target file by the temporary which now contains our entries
	mv $TMPFILE $OUTFILE;

	return 0;
}

#
# Patch the Kconfig file. This function assumes that there is some input Kconfig file
# (one placed in mtd) containing (among other) a block of continguous entries for
# some subsystem (e.g, JFFS2). Function reads this block and inserts it to the
# target Kconfig file (in Linux sources). Before inserting it removes older entries
# from the target Kconfig.
#
# Input positional parameters:
# 
# $1:	the path of the input Kconfig file from which the function
#	should fetch new the Kconfig entries;
# $2:	the path of the target linux Kconfig file which should be
#	patched; if the parameter value is '', just insert to the end of
#	the target Kconfig file;
# $3:	The patched subsystem name;
# $4:	the pattern in the target Kconfig file after which the new entries should
#	be inserted.
# 
function patch_Kconfig () {
	local INFILE=$1;
	local OUTFILE=$2
	local SUBSYS=$3;
	local MENUNAME=$4;
	
	if [ ! -f $INFILE ] || [ ! -r $INFILE ]; then
		echo -n "Error: the proper input file $INFILE does not exist";
		return 1;
	fi;
	
	if [ ! -f $OUTFILE ] || [ ! -r $OUTFILE ] || [ ! -w $OUTFILE ]; then
		echo -n "Error: the proper output Kconfig file $INFILE does not exist";
		return 1;
	fi;

	find_lines $OUTFILE "$SUBSYS";
	if [ $FIRSTLINE -gt $LASTLINE ]; then
		# Bug in find_lines function. Fix it.
		echo -n "Internal error: last > first";
		return 1;
	fi;
	local TOTALIN=`cat $OUTFILE | wc -l`;
	if [ $LASTLINE -gt $TOTALIN ]; then
		# Bug in find_lines function. Fix it.
		echo -n "Internal error: last > total";
		return 1;
	fi;
	
	# remove older SUBSYS-related entries from Kconfig
	local TMPFILE=$OUTFILE.$$;
	if [ $LASTLINE -ne "0" ]; then
		cat $OUTFILE | sed -n -e "$FIRSTLINE,$LASTLINE ! p" > $TMPFILE;
		mv $TMPFILE $OUTFILE;
	fi;

	find_lines "$INFILE" "$SUBSYS";
	if [ $FIRSTLINE -gt $LASTLINE ]; then
		# Bug in find_lines function. Fix it.
		echo -n "Internal error: last > first";
		return 1;
	fi;
	local TOTALIN=`cat $INFILE | wc -l`;
	if [ $LASTLINE -gt $TOTALIN ]; then
		# Bug in find_lines function. Fix it.
		echo -n "Internal error: last > total";
		return 1;
	fi;
	do_patch_Kconfig $INFILE $OUTFILE $FIRSTLINE $LASTLINE $MENUNAME;
}

#
# Patch the fs/Makfile file.
# 
# Function requires the following positional parameters:
# 
# $1:   the path of the Makefile
#
# $2:   pattern of the previous line, after it the third parameter will be inserted
#
# $3:   the line which should be inserted
#
# Note: the $3 will be inserted only if it is absent after $2.
#
function patch_fs_Makefile () {
	local INFILE=$1;
	local PREVLINE=$2;
	local NEWLINE=$3;

	local TOTAL=`cat $INFILE | wc -l`;

	# do not insert pattern if it is already present
	if [ "x`grep "$NEWLINE" $INFILE`" != "x" ]; then
		return 0;
	fi;
	
	# determine the previous line number
	local LINENUM='';
	LINENUM=`grep -m1 -n "$PREVLINE" $INFILE | sed -e 's/:.*//'`;
	
	# copy lines 1-LINENUM from the target file to the temporary file
	TMPFILE="Makefile.$$";
	sed -n "$INFILE" -e "1,$LINENUM p" > $TMPFILE;
	echo "$NEWLINE" >> $TMPFILE;
	let LINENUM=$LINENUM+1;
	
	sed -n "$INFILE" -e "$LINENUM,$TOTAL p" >> $TMPFILE;
	# replace the target file by the temporary which now contains our entries
	mv $TMPFILE $INFILE;

	return 0;
}


#
# Patch old kernels with JFFS/JFFS2 filesystems
# 
function legacy_fs_patch () {
	local PATCHDONE=`grep -s jffs2 fs/Makefile | head -n 1`
	if [ "$PATCHDONE" = "" ]; then
		echo "Add JFFS2 to Makefile and Config.in manually. JFFS2 is included as of 2.4.12"
		return 0;
	fi;
	
	local JFFS=`grep -n JFFS fs/Config.in | grep -v JFFS3 | head -n 1 | sed s/:.*//`
	local CRAMFS=`grep -n CRAMFS fs/Config.in | head -n 1 | sed s/:.*//`
	let JFFS=JFFS-1
	let CRAMFS=CRAMFS-1
	sed "$JFFS"q fs/Config.in >Config.tmp
	cat $TOPDIR/fs/Config.in >>Config.tmp
	sed 1,"$CRAMFS"d fs/Config.in >>Config.tmp
	mv -f Config.tmp fs/Config.in
	
	if [ -f include/linux/crc32.h ] 
	then
		# check, if it is already defined there
		local CRC32=`grep -s 'crc32(' include/linux/crc32.h | head -n 1`
		if [ "$CRC32" = "" ]
		then
			# patch in header from fs/jffs2
			local LASTLINE=`grep -n '#endif' include/linux/crc32.h | head -n 1 | sed s/:.*//`
			let LASTLINE=LASTLINE-1
			sed "$LASTLINE"q include/linux/crc32.h >Crc32.tmp
			cat fs/jffs2/crc32.h >>Crc32.tmp
			echo "#endif" >>Crc32.tmp
			mv -f Crc32.tmp include/linux/crc32.h
		fi
	else
		rm -f include/linux/crc32.h
		$LNCP $TOPDIR/fs/jffs2/crc32.h include/linux
	fi
}

#
# Display usage of this script
#
usage () {
	echo "usage:  $0 [-c] [-j] [-2] [-3] [-b] [-y] kernelpath"
	echo "   -c  -- copy files to kernel tree instead of building links"
	echo "   -j  -- include JFFS2 file system (depricated option)" 
	echo "   -2  -- include JFFS2 file system"
	echo "   -3  -- include JFFS3 file system (experimental, you probably don't want this)"
	echo "   -b  -- check files out for write from BK" 
	echo '   -y  -- assume "Yes" on any question'
	exit 1
}

# Function to patch kernel source
function patchit () {
	for DIR in $PATCH_DIRS 
	do
		echo $DIR
		mkdir -p $DIR
		cd $TOPDIR/$DIR
		FILES=`ls $PATCH_FILES 2>/dev/null`
		if [ "$BK" = "yes" -a -d $LINUXDIR/$DIR/SCCS ]; then
		    pushd $LINUXDIR/$DIR
		    rm -f $FILES
		    bk co -ql $FILES
		    popd
		fi
		for FILE in $FILES 
		do
			# If there's a Makefile.common it goes in place of Makefile
			if [ "$FILE" = "Makefile" -a -r $TOPDIR/$DIR/Makefile.common ]; then
			    if test $PATCHLEVEL -lt 5; then
				rm -f $LINUXDIR/$DIR/Makefile.common 2>/dev/null
				$LNCP $TOPDIR/$DIR/Makefile.common $LINUXDIR/$DIR/Makefile.common
				SRCFILE=Makefile.24
			    else
				SRCFILE=Makefile.common
			    fi
			else
			    SRCFILE=$FILE
			fi
			rm -f $LINUXDIR/$DIR/$FILE 2>/dev/null
			$LNCP $TOPDIR/$DIR/$SRCFILE $LINUXDIR/$DIR/$FILE
		done
		cd $LINUXDIR
	done	
}

# Preset variables
JFFS2_FS="no"
JFFS3_FS="no"
BK="no"
VERSION=0
PATCHLEVEL=0
SUBLEVEL=0
ZLIBPATCH="no"
RSLIBPATCH="no"
DOCPATCH="no"
CONFIG="Config.in"
LNCP="ln -sf"
METHOD="Link"
ASSUME_YES="no"

# MTD - files and directories
MTD_DIRS="drivers/mtd drivers/mtd/chips drivers/mtd/devices drivers/mtd/maps drivers/mtd/nand include/linux/mtd include/mtd"
MTD_FILES="*.[ch] Makefile Rules.make"

# JFFS2 files and directories
JFFS2_DIRS="fs/jffs2"
JFFS2_FILES="*.[ch] Makefile Rules.make"
# kernel version < 2.4.20 needs zlib headers
JFFS2_INC_BEL2420="jffs*.h workqueue.h z*.h rb*.h suspend.h"
# kernel version < 2.5.x
JFFS2_INC_BEL25="jffs2*.h workqueue.h rb*.h suspend.h"
# kernelversion >= 2.5
JFFS2_INC_25="jffs2*.h"
JFFS2_INC_DIR="include/linux"

# shared ZLIB patch
ZLIB_DIRS="lib/zlib_deflate lib/zlib_inflate"
ZLIB_FILES="*.[ch] Makefile"
# shared REED_SOLOMON patch
RSLIB_DIRS="lib/reed_solomon"
RSLIB_FILES="*.[ch]"
RSLIB_INC_DIR="include/linux"
RSLIB_INC="rslib.h"

# Documentation
DOC_DIRS="Documentation/DocBook"
DOC_FILES="*.tmpl"

# Experimental stuff
JFFS3_DIRS="fs/jffs3"
JFFS3_FILES="*.[ch] Makefile Rules.make"
JFFS3_INC="jffs3.h"
JFFS3_INC_DIR="fs/jffs3"

# Make text utils not suck
export LANG=C
export LC_ALL=C

#
# Start of script
#

# Get commandline options
while getopts j23cby opt
do
    case "$opt" in
      j)  JFFS2_FS=yes;;
      2)  JFFS2_FS=yes;;
      3)  JFFS3_FS=yes;;
      c)  LNCP="cp -f"; METHOD="Copy";;
      b)  BK=yes;;
      y)  ASSUME_YES="yes";;
      \?)
	  usage;
    esac
done
shift `expr $OPTIND - 1`
LINUXDIR=$1

if [ -z $LINUXDIR ]; then
    usage;
fi

if [ ! -f $LINUXDIR/Makefile -a "$BK" = "yes" ]; then
    pushd $LINUXDIR
    bk co Makefile
    popd
fi

# Check if kerneldir contains a Makefile
if [ ! -f $LINUXDIR/Makefile ] 
then 
	echo "Directory $LINUXDIR does not exist or is not a kernel source directory";
	exit 1;
fi

# Get kernel version
VERSION=`grep -s VERSION <$LINUXDIR/Makefile | head -n 1 | sed s/'VERSION = '//`
PATCHLEVEL=`grep -s PATCHLEVEL <$LINUXDIR/Makefile | head -n 1 | sed s/'PATCHLEVEL = '//`
SUBLEVEL=`grep -s SUBLEVEL <$LINUXDIR/Makefile | head -n 1 | sed s/'SUBLEVEL = '//`

# Can we handle this ?
if test $VERSION -ne 2 -o $PATCHLEVEL -lt 4
then 
	echo "Cannot patch kernel version $VERSION.$PATCHLEVEL.$SUBLEVEL";
	exit 1;
fi

# Use Kconfig instead of Config.in for Kernels >= 2.5
if test $PATCHLEVEL -gt 4
then
	CONFIG="Kconfig";
fi
MTD_FILES="$MTD_FILES $CONFIG"

# Have we to use ZLIB PATCH ? 
if [ "$JFFS2_FS" = "yes" ]
then
	PATCHDONE=`grep -s zlib_deflate $LINUXDIR/lib/Makefile | head -n 1`
	if test $PATCHLEVEL -eq 4 -a $SUBLEVEL -lt 20 
	then
		if [ "$PATCHDONE" = "" ] 
		then
			ZLIBPATCH=yes;
		fi
	fi
fi

# Have we to use REED_SOLOMON PATCH ?
PATCHDONE=`grep -s reed_solomon $LINUXDIR/lib/Makefile | head -n 1`
if [ "$PATCHDONE" = "" ]
then
	RSLIBPATCH=yes;
fi

# Have we to use DOCUMENTATION PATCH ?
PATCHDONE=`grep -s mtdnand $LINUXDIR/$DOC_DIRS/Makefile | head -n 1`
if [ "$PATCHDONE" = "" ]
then
    if test $PATCHLEVEL -gt 4 
    then
	DOCPATCH=yes;
    fi
fi

# Check which header files we need depending on kernel version
HDIR="include/linux"

if test $JFFS3_FS = 'yes' && test $PATCHLEVEL -lt 6
then
	echo "JFFS3 works only with >= 2.6 kernels"
	exit 1
fi;

if test $PATCHLEVEL -eq 4 
then	
	# 2.4 below 2.4.20 zlib headers are neccecary
	if test $SUBLEVEL -lt 20
	then
		JFFS2_H=$JFFS2_INC_BEL2420
	else
		JFFS2_H=$JFFS2_INC_BEL25
	fi
else
	#	>= 2.5
	JFFS2_H=$JFFS2_INC_25
fi

echo "Patching $LINUXDIR"
echo "Include JFFS2 file system: $JFFS2_FS"
echo "Include JFFS3 file system (experimental): $JFFS3_FS"
echo "Zlib-Patch needed: $ZLIBPATCH"
echo "RS-Lib-Patch needed: $RSLIBPATCH"
echo "Documentation Patch needed: $DOCPATCH"
echo "Method: $METHOD"

if [ $ASSUME_YES != "yes" ]; then
	read -p "Can we start now ? [y/N]" ANSWER
	echo ""
	if [ "$ANSWER" != "y" ]; then
		echo Patching Kernel cancelled
		exit 1;
	fi
fi

# Here we go
cd `dirname $0`
THISDIR=`pwd`
TOPDIR=`dirname $THISDIR`

cd $LINUXDIR

# make directories, if necessary
# remove existing files/links and link/copy the new ones
echo "Patching MTD"
PATCH_DIRS=$MTD_DIRS
PATCH_FILES=$MTD_FILES
patchit;

# some BUG() definitions were moved to asm/bug.h in the 2.5 kernels
# so fake having one to avoid build errors.
if test $PATCHLEVEL -lt 5; then
	if [ ! -r $LINUXDIR/include/asm/bug.h ]; then
		touch $LINUXDIR/include/asm/bug.h
	fi
fi

if test $PATCHLEVEL -lt 5
then 
	# FIXME: SED/AWK experts should know how to do it automagic
	echo "Please update Documentation/Configure.help from $TOPDIR/Documentation/Configure.help"
fi

if [ "$ZLIBPATCH" = "yes" ]
then
	echo "Patching ZLIB"
	
	PATCH_DIRS=$ZLIB_DIRS
	PATCH_FILES=$ZLIB_FILES
	patchit;

	patch -p1 -i $TOPDIR/lib/patch-Makefile
fi

echo "Patching RS Lib"
if [ "$RSLIBPATCH" = "yes" ]
then
	if test $PATCHLEVEL -eq 4 
	then
		patch -p1 -i $TOPDIR/lib/Makefile24-rs.diff
		patch -p1 -i $TOPDIR/lib/Config.in-rs.diff
	else
		patch -p1 -i $TOPDIR/lib/Makefile26-rs.diff
		patch -p1 -i $TOPDIR/lib/Kconfig-rs.diff
	fi
	
	mkdir -p lib/reed_solomon
	
fi

	PATCH_DIRS=$RSLIB_DIRS
	PATCH_FILES=$RSLIB_FILES
	patchit;

	PATCH_DIRS=$RSLIB_INC_DIR
	PATCH_FILES=$RSLIB_INC
	patchit;
	if test $PATCHLEVEL -eq 6 
	then
		mv -f lib/reed_solomon/rslib.c lib/reed_solomon/reed_solomon.c
	fi

if test $PATCHLEVEL -eq 4 
then
	PATCH_DIRS=$RSLIB_DIRS
	PATCH_FILES="Makefile24"
	patchit;
	rm -f $LINUXDIR/lib/reed_solomon/Makefile 2>/dev/null
	mv -f $LINUXDIR/lib/reed_solomon/Makefile24 $LINUXDIR/lib/reed_solomon/Makefile
else	
	PATCH_DIRS=$RSLIB_DIRS
	PATCH_FILES="Makefile26"
	patchit;
	rm -f $LINUXDIR/lib/reed_solomon/Makefile 2>/dev/null
	mv -f $LINUXDIR/lib/reed_solomon/Makefile26 $LINUXDIR/lib/reed_solomon/Makefile
fi

echo "Patching Documentation"
if [ "$DOCPATCH" = "yes" ]
then
    patch -p1 -i $TOPDIR/$DOC_DIRS/Makefile.diff
fi

PATCH_DIRS=$DOC_DIRS
PATCH_FILES=$DOC_FILES
patchit;

# check, if we have to include JFFS2
if [ "$JFFS2_FS" = "yes" ]
then
	echo "Patching JFFS2"
	
	PATCH_DIRS=$JFFS2_DIRS
	PATCH_FILES=$JFFS2_FILES
	patchit;

	PATCH_DIRS=$JFFS2_INC_DIR
	PATCH_FILES=$JFFS2_H
	patchit;

	if test $PATCHLEVEL -lt 5; then 
		legacy_fs_patch;
	else
		patch_Kconfig "$TOPDIR/fs/Kconfig" "fs/Kconfig" "JFFS2" "Miscellaneous filesystems";
	fi;
fi

# check, if we have to include experimental stuff
if [ "$JFFS3_FS" = "yes" ]
then
	echo "Patching JFFS3 (experimental)"
	
	PATCH_DIRS=$JFFS3_DIRS;
	PATCH_FILES=$JFFS3_FILES;
	patchit;

	PATCH_DIRS=$JFFS3_INC_DIR;
	PATCH_FILES=$JFFS3_INC;
	patchit;
	
	patch_Kconfig "$TOPDIR/fs/Kconfig" "fs/Kconfig" "JFFS3" "Miscellaneous filesystems";
	patch_fs_Makefile "fs/Makefile" 'CONFIG_JFFS2_FS' 'obj-$(CONFIG_JFFS3_FS)		+= jffs3/'
fi

echo "Patching done"

