#! /bin/sh -e
# Script to handle kernel patches intelligently.

usage()
{
    echo Usage: lkpatch [--keep-broken] [--depends-only] [--optional-depends] [directory] [patch...] >&2
    echo Takes one or more patches, and applies them to the kernel in the >&2
    echo specified directory \(or current working directory if none given\). >&2
    echo >&2
    echo If patch is not specified, stdin is used.  Otherwise, if the patch >&2
    echo contains a Depends: line, apply those first, if not already applied. >&2
    exit 1
}

# Takes source dir, clone dir, and file list.
clone_tree()
{
    SRC=$1
    DST=$2
    shift 2
    for f; do
	[ -d `dirname $DST/$f` ] || mkdir -p `dirname $DST/$f`
	! [ -f $SRC/$f ] || cp $SRC/$f $DST/$f
    done
}

# Copy files back.  If they don't exist, erase in master tree.
# Takes source dir, dest dir, and file list
copy_back()
{
    SRC=$1
    DST=$2
    shift 2
    for f; do
	# Break hard links!
	rm -f $DST/$f
	if [ -f $SRC/$f ]; then
	    # Little-known fact: patch can create directories.
	    DIR=$(dirname $DST/$f)
	    [ -d "$DIR" ] || mkdir -p "$DIR"
	    cp $SRC/$f $DST/$f
	fi
    done
}

# Find dependency $1 in dir $2.
find_dep()
{
    find_dep_file="$1"
    find_dep_dir="$2"

    # Absolute file path.
    case "$find_dep_file" in
	/*) echo "$find_dep_file"; return;;
    esac

    # For every dir in file, take one dir off directory.
    while [ "$(dirname $find_dep_file)" != . ]; do
	find_dep_dir="$(dirname $find_dep_dir)"
	find_dep_file="$(dirname $find_dep_file)"
    done

    # List all variants from most recent backwards.
    find_dep_wildcard=$(echo "$find_dep_dir"/"$1" | sed 's/gz$//')
    ls -t "$find_dep_wildcard"*gz
}

# Takes source directory, and list of dependencies...
do_depends()
{
    DEP_SOURCE_DIR=$1
    shift
    for dep; do
	dep_files=`find_dep $dep $DEPDIR`
	if [ x"$dep_files" = x ]; then
	    echo $0 error: could not find dependency "$dep" >&2
	    [ $OPTIONAL_DEPENDS = 1 ] || exit 1
	else
	    for dep_possible in $dep_files; do
		if isapplied $DEP_SOURCE_DIR $dep_possible >/dev/null ||
		    $0 $DEP_SOURCE_DIR $dep_possible
		then
		    continue 2
		fi
	    done
	    echo $0 error: Could not apply dependency "$dep". >&2
	    exit 1
	fi
    done
}

KEEP_BROKEN=0
DEPENDS_ONLY=0
OPTIONAL_DEPENDS=0

while true; do
    case "$1" in
    --keep-broken) KEEP_BROKEN=1;;
    --depends-only) DEPENDS_ONLY=1;;
    --optional-depends) OPTIONAL_DEPENDS=1;;
    --) shift; break;;

    -*)
	echo Unknown argument "$1" >&2
	usage
	;;
    *)
	# No more args
	break;
    esac
    shift
done

# Set KDIR to canonical (no trailing / or /. allowed)
case "$1" in
    /*) KDIR="$1";;
    *) KDIR="$(pwd)/$1";;
esac
KDIR=$(echo "$KDIR" | sed -e 's:\(/*\.\)*/*$::')
[ x"$1" = x ] || shift

# Set bogus stdin argument if no args.
if [ x"$1" = x"" ]; then set -- ""; fi

[ -d "$KDIR" ] || usage

TMPFILE=`mktemp /tmp/$$.XXXXXX`
trap "rm -rf $TMPFILE $KDIR.tmp.$$" EXIT

STARTDIR=$(pwd)

for PATCH; do
    if [ x"$PATCH" = x ]; then DEPDIR=""; else
	# Canonicalize relative paths.
	case "$PATCH" in
	    /*) ;;
	    *) PATCH="$STARTDIR/$PATCH";;
	esac
	DEPDIR=$(dirname $PATCH)
    fi

    case "$PATCH" in
	*.bz2) CAT=bzcat;;
	*.gz) CAT=zcat;;
	*) CAT=cat;;
    esac

    # Do Depends, if any
    [ x"$DEPDIR" = x ] ||
	do_depends $KDIR $($CAT $PATCH | grep '^Depends:' | cut -d: -f2-)
    [ x"$DEPENDS_ONLY" = x0 ] || continue

    if [ x"$PATCH" = x ]; then
	# Don't know what's coming: copy whole tree.
	cp -al $KDIR $KDIR.tmp.$$
    else
	mkdir $KDIR.tmp.$$
	FILELIST=$($CAT $PATCH | grep '^+++ ' | cut -d/ -f2- | cut -d'	' -f1)
	clone_tree $KDIR $KDIR.tmp.$$ $FILELIST
    fi

    if $CAT $PATCH | (cd $KDIR.tmp.$$ && patch -s -p1); then :
    else
	if [ $KEEP_BROKEN -eq 1 ]; then
	    if [ x"$PATCH" = x ]; then
		# Copy back those with linkcount == 1.
		FILELIST=$(cd $KDIR.tmp.$$ && find . -type f -links 1)
	    fi
	    copy_back $KDIR.tmp.$$/ $KDIR $FILELIST $(for f in $FILELIST; do echo $f.rej; done)
	fi
	exit 1
    fi
    if [ x"$PATCH" = x ]; then
	# Copy back those with linkcount == 1.
	FILELIST=$(cd $KDIR.tmp.$$ && find . -type f -links 1)
    fi
    copy_back $KDIR.tmp.$$/ $KDIR $FILELIST
    rm -r $KDIR.tmp.$$
    echo Applied $PATCH...
done

exit 0