#!/bin/bash
#
# livepatch build script
#
# Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
# Copyright (C) 2013,2014 Josh Poimboeuf <jpoimboe@redhat.com>
# Copyright (C) 2015 Ross Lagerwall <ross.lagerwall@citrix.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This script takes a Xen tree, and a patch and outputs an livepatch
# module intended to patch Xen at runtime.
# Large amounts of this script are taken from kpatch's kpatch-build
# script.

SCRIPTDIR="$(readlink -f $(dirname $(type -p $0)))"
CPUS="$(getconf _NPROCESSORS_ONLN)"
DEBUG=n
XEN_DEBUG=n
SKIP=
DEPENDS=
PRELINK=
XENSYMS=xen-syms

warn() {
    echo "ERROR: $1" >&2
}

die() {
    if [[ -z $1 ]]; then
        msg="LivePatch build failed"
    else
        msg="$1"
    fi

    warn "$msg."

    exit 1
}

find_tools() {
    if [[ -e "$SCRIPTDIR/create-diff-object" ]]; then
        # Running from source tree
        TOOLSDIR="$SCRIPTDIR"
    elif [[ -e "$SCRIPTDIR/../libexec/livepatch-build-tools/create-diff-object" ]]; then
        # Running installed
        TOOLSDIR="$(readlink -f $SCRIPTDIR/../libexec/livepatch-build-tools)"
    else
        return 1
    fi
}

function make_patch_name()
{
    PATCHNAME=$(basename "$1")
    if [[ "$PATCHNAME" =~ \.patch ]] || [[ "$PATCHNAME" =~ \.diff ]]; then
            PATCHNAME="${PATCHNAME%.*}"
    fi

    # Only allow alphanumerics and '_' and '-' in the patch name.  Everything
    # else is replaced with '-'.  Truncate to 48 chars.
    echo ${PATCHNAME//[^a-zA-Z0-9_-]/-} |cut -c 1-48
}

# Do a full normal build
function build_full()
{
    cd "${SRCDIR}/xen" || die
    make "-j$CPUS" clean &> "${OUTPUT}/build_full_clean.log" || die
    make "-j$CPUS" $XEN_DEBUG &> "${OUTPUT}/build_full_compile.log" || die
    cp xen-syms "$OUTPUT"
}

# Build with special GCC flags
function build_special()
{
    name=$1

    cd "${SRCDIR}" || die

    # Capture .o files from the patched build
    export CROSS_COMPILE="${TOOLSDIR}/livepatch-gcc "
    export LIVEPATCH_BUILD_DIR="$(pwd)/"
    export LIVEPATCH_CAPTURE_DIR="$OUTPUT/${name}"
    mkdir -p "$LIVEPATCH_CAPTURE_DIR"

    # Build with special GCC flags
    cd "${SRCDIR}/xen" || die
    sed -i 's/CFLAGS += -nostdinc/CFLAGS += -nostdinc -ffunction-sections -fdata-sections/' Rules.mk
    cp -p arch/x86/Makefile arch/x86/Makefile.bak
    sed -i 's/--section-alignment=0x200000/--section-alignment=0x1000/' arch/x86/Makefile
    # Restore timestamps to prevent spurious rebuilding
    touch --reference=arch/x86/Makefile.bak arch/x86/Makefile
    make "-j$CPUS" $XEN_DEBUG &> "${OUTPUT}/build_${name}_compile.log" || die
    sed -i 's/CFLAGS += -nostdinc -ffunction-sections -fdata-sections/CFLAGS += -nostdinc/' Rules.mk
    mv -f arch/x86/Makefile.bak arch/x86/Makefile

    unset LIVEPATCH_BUILD_DIR
    unset LIVEPATCH_CAPTURE_DIR
}

function create_patch()
{
    echo "Extracting new and modified ELF sections..."

    [[ -e "${OUTPUT}/original/changed_objs" ]] || die "no changed objects found"
    [[ -e "${OUTPUT}/patched/changed_objs" ]] || die "no changed objects found"

    cd "${OUTPUT}/original" || die
    FILES="$(find xen -type f -name "*.o")"
    cd "${OUTPUT}" || die
    CHANGED=0
    ERROR=0
    debugopt=
    [[ $DEBUG -eq 1 ]] && debugopt=-d

    for i in $FILES; do
        mkdir -p "output/$(dirname $i)" || die
        echo "Processing ${i}"
        echo "Run create-diff-object on $i" >> "${OUTPUT}/create-diff-object.log"
        "${TOOLSDIR}"/create-diff-object $debugopt $PRELINK "original/$i" "patched/$i" "$XENSYMS" "output/$i" &>> "${OUTPUT}/create-diff-object.log"
        rc="${PIPESTATUS[0]}"
        if [[ $rc = 139 ]]; then
            warn "create-diff-object SIGSEGV"
            if ls core* &> /dev/null; then
                cp core* /tmp
                die "core file at /tmp/$(ls core*)"
            fi
            die "no core file found, run 'ulimit -c unlimited' and try to recreate"
        fi
        # create-diff-object returns 3 if no functional change is found
        [[ $rc -eq 0 ]] || [[ $rc -eq 3 ]] || ERROR=$(expr $ERROR "+" 1)
        if [[ $rc -eq 0 ]]; then
            CHANGED=1
        fi
    done

    if [[ $ERROR -ne 0 ]]; then
        die "$ERROR error(s) encountered"
    fi

    if [[ $CHANGED -eq 0 ]]; then
        die "no functional changes found"
    fi

    # Create a dependency section
    perl -e "print pack 'VVVZ*H*', 4, 20, 3, 'GNU', '${DEPENDS}'" > depends.bin

    echo "Creating patch module..."
    if [ -z "$PRELINK" ]; then
        ld -r -o "${PATCHNAME}.livepatch" --build-id=sha1 $(find output -type f -name "*.o") || die
        chmod +x "${PATCHNAME}.livepatch"
    else
        ld -r -o output.o --build-id=sha1 $(find output -type f -name "*.o") || die
        "${TOOLSDIR}"/prelink $debugopt output.o "${PATCHNAME}.livepatch" "$XENSYMS" &>> "${OUTPUT}/prelink.log" || die
    fi

    objcopy --add-section .livepatch.depends=depends.bin "${PATCHNAME}.livepatch"
    objcopy --set-section-flags .livepatch.depends=alloc,readonly "${PATCHNAME}.livepatch"
}

usage() {
    echo "usage: $(basename $0) [options]" >&2
    echo "        -h, --help         Show this help message" >&2
    echo "        -s, --srcdir       Xen source directory" >&2
    echo "        -p, --patch        Patch file" >&2
    echo "        -c, --config       .config file" >&2
    echo "        -o, --output       Output directory" >&2
    echo "        -j, --cpus         Number of CPUs to use" >&2
    echo "        -k, --skip         Skip build or diff phase" >&2
    echo "        -d, --debug        Enable debug logging" >&2
    echo "        --xen-debug        Build debug Xen (if your .config does not have the options)" >&2
    echo "        --xen-syms         Build against a xen-syms" >&2
    echo "        --depends          Required build-id" >&2
    echo "        --prelink          Prelink" >&2
}

find_tools || die "can't find supporting tools"

options=$(getopt -o hs:p:c:o:j:k:d -l "help,srcdir:,patch:,config:,output:,cpus:,skip:,debug,xen-debug,xen-syms:,depends:,prelink" -- "$@") || die "getopt failed"

eval set -- "$options"

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)
            usage
            exit 0
            ;;
        -j|--cpus)
            shift
            CPUS="$1"
            shift
            ;;
        -k|--skip)
            shift
            SKIP="$1"
            shift
            ;;
        -d|--debug)
            DEBUG=1
            shift
            ;;
        --xen-debug)
            XEN_DEBUG=y
            shift
            ;;
        -s|--srcdir)
            shift
            srcarg="$1"
            shift
            ;;
        -p|--patch)
            shift
            patcharg="$1"
            shift
            ;;
        -c|--config)
            shift
            configarg="$1"
            shift
            ;;
        -o|--output)
            shift
            outputarg="$1"
            shift
            ;;
        --xen-syms)
            shift
            XENSYMS="$(readlink -m -- "$1")"
            [ -f "$XENSYMS" ] || die "xen-syms file does not exist"
            shift
            ;;
        --depends)
            shift
            DEPENDS="$1"
            shift
            ;;
        --prelink)
            PRELINK=--resolve
            shift
            ;;
        --)
            shift
            break
            ;;
    esac
done

[ -z "$srcarg" ] && die "Xen directory not given"
[ -z "$patcharg" ] && die "Patchfile not given"
[ -z "$configarg" ] && die ".config not given"
[ -z "$outputarg" ] && die "Output directory not given"
[ -z "$DEPENDS" ] && die "Build-id dependency not given"

SRCDIR="$(readlink -m -- "$srcarg")"
PATCHFILE="$(readlink -m -- "$patcharg")"
CONFIGFILE="$(readlink -m -- "$configarg")"
OUTPUT="$(readlink -m -- "$outputarg")"

[ -d "${SRCDIR}" ] || die "Xen directory does not exist"
[ -f "${PATCHFILE}" ] || die "Patchfile does not exist"
[ -f "${CONFIGFILE}" ] || die ".config does not exist"

PATCHNAME=$(make_patch_name "${PATCHFILE}")

echo "Building LivePatch patch: ${PATCHNAME}"
echo
echo "Xen directory: ${SRCDIR}"
echo "Patch file: ${PATCHFILE}"
echo ".config file: ${CONFIGFILE}"
echo "Output directory: ${OUTPUT}"
echo "================================================"
echo

if [ "${SKIP}" != "build" ]; then
    [ -e "${OUTPUT}" ] && die "Output directory exists"
    grep -q 'CONFIG_LIVEPATCH=y' "${CONFIGFILE}" || die "CONFIG_LIVEPATCH must be enabled"
    cd "$SRCDIR" || die
    patch -s -N -p1 -f --fuzz=0 --dry-run < "$PATCHFILE" || die "Source patch file failed to apply"

    mkdir -p "${OUTPUT}" || die
    cp -f "${CONFIGFILE}" "${OUTPUT}/.config"
    cp -f "${OUTPUT}/.config" "xen/.config"

    grep -q CONFIG_DEBUG "xen/.config"
    if [ $? -eq 0 ]; then
        if [ "$XEN_DEBUG" == "y" ]; then
            grep -q "CONFIG_DEBUG=y" "xen/.config" || die "CONFIG_DEBUG and --xen-debug mismatch"
        fi
        XEN_DEBUG=""
    else
        XEN_DEBUG="debug=$XEN_DEBUG"
    fi

    echo "Perform full initial build with ${CPUS} CPU(s)..."
    build_full

    echo "Apply patch and build with ${CPUS} CPU(s)..."
    cd "$SRCDIR" || die
    patch -s -N -p1 -f --fuzz=0 < "$PATCHFILE" || die
    build_special patched

    echo "Unapply patch and build with ${CPUS} CPU(s)..."
    cd "$SRCDIR" || die
    patch -s -R -p1 -f --fuzz=0 < "$PATCHFILE" || die
    build_special original
fi

if [ "${SKIP}" != "diff" ]; then
    [ -d "${OUTPUT}" ] || die "Output directory does not exist"

    cd "${OUTPUT}" || die
    create_patch
    echo "${PATCHNAME}.livepatch created successfully"
fi
