#!/bin/bash
#
# Copyright (C) 2010 Bart Trojanowski <bart@jukie.net>
#
# This script applies saref, natt, and klips patches to a
# linux kernel git tree.

set -e

prog=$(basename $0)

say() {
        echo >&2 "$@"
}
warn() {
        say "$prog: $@"
}
die() {
        warn "$@"
        exit 1
}

do_help() {
        local err=$1
        local out=1
        [[ -n "$err" && "$err" -eq 0 ]] || out=2
        cat >&$out <<END
This is a tool that applies libreswan patches to a kernel git tree.

Syntax

  $prog [ -s | --saref ] [ -n | --natt ] [ -k | --klips ] <path to kernel>

Options:

  -s --saref                    - apply the saref patch(es), create commit(s)
  -n --natt                     - apply the natt patch(es), create commit(s)
  -k --klips                    - apply the klips patch(es), create commit(s)

Generic options:

  -h --help                     - this help
  --dry-run                     - don't apply anything
  -q --quiet                    - pass quiet flag to git-am
  -t --tmp-dir <dir>            - use this temporary directory

END
        exit $err
}

# set defaults
do_saref=false
do_natt=false
do_klips=false
dryrun_mode=false
cleen_up=true
arg_kernel=
arg_quiet=
arg_tmpdir=/tmp
libreswan_rev=
kernel_ver=

# this will queue a patch if it looks like it would apply
queue_patch() {
        local queue="$1"
        local pnum="$2"
        local patch="$3"

        # add it to the queue
        cat "$patch" >> "$queue"

        # check if the patch applies (including all the ones that came before it)
        (
        cd "$arg_kernel"
        git apply --ignore-space-change --ignore-whitespace --check -p"$pnum" "$queue" || die "patch doesn't apply"
        )
}

# apply the patch in the kernel tree
apply_a_patch() {
        local pnum="$1"
        local patch="$2"
        local desc="$3"

        $dryrun_mode && return 0

        # import the patch
        (
        cd "$arg_kernel"
        git apply --ignore-space-change --ignore-whitespace --index < "$patch"
        diffstat -l -p"$pnum" < "$patch" | xargs git add
        git commit -m"$desc"
        )
}

is_git_dir() {
        local dir="$1"
        [[ -d "$dir" \
        && -f "$dir/HEAD" \
        && -d "$dir/objects" \
        && -d "$dir/refs" ]]
}

generate_saref_patch() {
        local workdir="$1"
        local series="$2"
        local count=0

        for src in patches/kernel/$kernel_ver/0*.patch ; do
                count=$(($count + 1))
                local patch="$workdir/saref$count.patch"
                local desc="Applied saref patch #$count from Libreswan $libreswan_rev"
                local pnum=1

                say "Reading ${src##*/}..."
                say "Generating ${patch##*/}..."

                cat "$src" > "$patch"

                echo "$pnum" "$patch" "$desc" >> $series
        done
}

generate_natt_patch() {
        local workdir="$1"
        local series="$2"
        local patch="$workdir/natt.patch"
        local desc="Applied NATT patch from Libreswan $libreswan_rev"
        local pnum=1

        say "Generating ${patch##*/}..."

        cat "patches/kernel/$kernel_ver/natt.patch" > "$patch"

        echo "$pnum" "$patch" "$desc" >> $series
}

generate_klips_patch() {
        local workdir="$1"
        local series="$2"
        local patch="$workdir/klips.patch"
        local desc="Applied klips patch from Libreswan $libreswan_rev"
        local pnum=1

        say "Generating ${patch##*/}..."

        make kernelpatch2.6 > "$patch"

        echo "$pnum" "$patch" "$desc" >> $series
}

apply_patches() {
        local workdir="$arg_tmpdir/$prog-$$"
        local series="$workdir/series"
        local queue="$workdir/queue"

        [[ -d $workdir ]] && die "$workdir already exists, remove it"
        mkdir -p "$workdir" || die "failed to create: $workdir"
        if $cleen_up ; then
                trap "rm -rf $workdir" EXIT HUP INT QUIT ABRT
        else
                trap "echo 'not cleaning: $workdir'" EXIT HUP INT QUIT ABRT
        fi

        $do_saref && generate_saref_patch "$workdir" "$series"
        $do_natt  && generate_natt_patch  "$workdir" "$series"
        $do_klips && generate_klips_patch "$workdir" "$series"

        while read pnum patch desc ; do
                local name=$(basename "$patch")

                say "Testing $name..."
                queue_patch "$queue" "$pnum" "$patch"
        done < "$series"

        while read pnum patch desc ; do
                local name=$(basename "$patch")

                say "Applying $name..."
                apply_a_patch "$pnum" "$patch" "$desc"
        done < "$series"

        say "DONE"
}

# parse parameters
while [[ -n "$1" ]] ; do
        [[ -n "$arg_kernel" ]] && die "garbage at end of line; see $prog --help"
        cmd="$1"
        shift
        case "$cmd" in
            -h|--help)
                do_help 0
                ;;
            --dry-run)
                dryrun_mode=true
                ;;
            --debug)
                set -x
                ;;
            --keep)
                cleen_up=false
                ;;
            -q|--quiet)
                arg_quiet="--quiet"
                ;;
            -t|--tmp-dir)
                arg_tmpdir="$1"
                [[ -d "$arg_tmpdir" ]] || die "no such directory: $arg_tmpdir"
                shift
                ;;
            -s|--saref)
                do_saref=true
                ;;
            -n|--natt)
                do_natt=true
                ;;
            -k|--klips)
                do_klips=true
                ;;
            -*)
                warn "invalid option: $cmd"
                do_help 1
                ;;
            *)
                arg_kernel="$cmd"
                ;;
        esac
done

( $do_saref || $do_natt || $do_klips ) || die "need to specify at least one of --saref, --natt, or --klips"

is_git_dir ".git" && libreswan_rev=$(git describe)
[[ "${libreswan_rev:0:1}" = "v" ]] || die "run this script from the top of the libreswan git tree"
echo "Libreswan revision $libreswan_rev"

[[ -n "$arg_kernel" ]] || die "missing kernel directory; see $prog --help"
is_git_dir "$arg_kernel/.git" && kernel_ver=$(git --git-dir="$arg_kernel/.git" describe | sed -ne's/v\(2\.6\.[0-9]\+\).*/\1/p')
[[ "${kernel_ver:0:2}" = "2." ]] || die "$arg_kernel: this dones't look like a kernel git tree"

apply_patches

exit $ret
