kpatch/kpatch-build/kpatch-build
Josh Poimboeuf d4363e0c9b kpatch-build: fix SCRIPTDIR for bash -x
More adventures in bash-land.  Running "bash -x kpatch-build foo.patch"
causes SCRIPTDIR to not get set properly.  This fixes that.
2014-03-14 11:09:29 -05:00

246 lines
7.5 KiB
Bash
Executable File

#!/bin/bash
#
# kpatch build script
#
# Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
# Copyright (C) 2013,2014 Josh Poimboeuf <jpoimboe@redhat.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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
# 02110-1301, USA.
# This script takes a patch based on the version of the kernel
# currently running and creates a kernel module that will
# replace modified functions in the kernel such that the
# patched code takes effect.
# This script currently only works on Fedora and will need to be adapted to
# work on other distros.
# This script:
# - Downloads the kernel src rpm for the currently running kernel
# - Unpacks and prepares the src rpm for building
# - Builds the base kernel (vmlinux)
# - Builds the patched kernel and monitors changed objects
# - Builds the patched objects with gcc flags -f[function|data]-sections
# - Runs kpatch tools to create and link the patch kernel module
BASE="$PWD"
LOGFILE="/tmp/kpatch-build-$(date +%s).log"
SCRIPTDIR="$(readlink -f $(dirname $(type -p $0)))"
ARCHVERSION="$(uname -r)"
DISTROVERSION="${ARCHVERSION%*.*}"
LOCALVERSION="-${ARCHVERSION##*-}"
CPUS="$(grep -c ^processor /proc/cpuinfo)"
CACHEDIR="$HOME/.kpatch"
SRCDIR="$CACHEDIR/$ARCHVERSION/src"
OBJDIR="$CACHEDIR/$ARCHVERSION/obj"
OBJDIR2="$CACHEDIR/$ARCHVERSION/obj2"
TEMPDIR=
STRIPCMD="strip -d --keep-file-symbols"
APPLIEDPATCHFILE="applied-patch"
die() {
if [[ -z $1 ]]; then
echo "ERROR: kpatch build failed. Check $LOGFILE for more details." >&2
else
echo "ERROR: $1" >&2
fi
exit 1
}
cleanup() {
rm -rf "$TEMPDIR"
if [[ -e "$SRCDIR/$APPLIEDPATCHFILE" ]]; then
patch -p1 -R -d "$SRCDIR" < "$SRCDIR/$APPLIEDPATCHFILE" &> /dev/null
rm -f "$SRCDIR/$APPLIEDPATCHFILE"
fi
}
find_data_dir() {
# git repo
DATADIR="$(readlink -f $SCRIPTDIR/../kmod)"
[[ -e "$DATADIR" ]] && return
# installation path
DATADIR="$(readlink -f $SCRIPTDIR/../share/kpatch)"
[[ -e "$DATADIR" ]] && return
return 1
}
find_tools_dir() {
#git repo
TOOLSDIR="$SCRIPTDIR"
[[ -e "$TOOLSDIR/create-diff-object" ]] && return
#installation path
TOOLSDIR="$(readlink -f $SCRIPTDIR/../libexec/kpatch)"
[[ -e "$TOOLSDIR/create-diff-object" ]] && return
return 1
}
usage() {
echo "usage: $0 [-s|--sourcedir <dir>] <patch file>" >&2
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-s|--sourcedir)
shift
[[ "$#" -eq 0 ]] && die "no source dir specified"
USERSRCDIR="$(readlink -f $1)"
[[ ! -d "$USERSRCDIR" ]] && die "source dir $1 not found"
shift
;;
*)
[[ -n "$PATCHFILE" ]] && die "bad argument: $1"
PATCHFILE="$(readlink -f $1)"
[[ ! -f "$PATCHFILE" ]] && die "patch file $1 not found"
shift
;;
esac
done
if [[ -z "$PATCHFILE" ]]; then
usage
exit 1
fi
PATCHNAME="$(basename $PATCHFILE)"
if [[ "$PATCHNAME" =~ \.patch ]] || [[ "$PATCHNAME" =~ \.diff ]]; then
PATCHNAME="${PATCHNAME%.*}"
fi
TEMPDIR="$(mktemp -d)" || die "mktemp failed"
trap cleanup EXIT INT TERM
find_data_dir || (echo "can't find data dir" >&2 && die)
find_tools_dir || (echo "can't find tools dir" >&2 && die)
if [[ -d "$SRCDIR" ]] || [[ -n "$USERSRCDIR" ]]; then
if [[ -n "$USERSRCDIR" ]]; then
SRCDIR="$CACHEDIR/src"
OBJDIR="$CACHEDIR/obj"
OBJDIR2="$CACHEDIR/obj2"
rm -rf "$CACHEDIR"
mkdir -p "$CACHEDIR"
mkdir -p "$OBJDIR" "$OBJDIR2"
cp "$USERSRCDIR/.config" "$OBJDIR" || die "source dir is missing a .config file"
echo "Copying source to $SRCDIR"
cp -a "$USERSRCDIR" "$SRCDIR" || die "copy failed"
else
echo "Using cache at $SRCDIR"
fi
else
rpm -q --quiet rpmdevtools || die "rpmdevtools not installed"
rpm -q --quiet yum-utils || die "yum-utils not installed"
echo "Downloading kernel source for $ARCHVERSION"
yumdownloader --source --destdir "$TEMPDIR" "kernel-$ARCHVERSION" >> "$LOGFILE" 2>&1 || die
echo "Unpacking kernel source"
rpmdev-setuptree >> "$LOGFILE" 2>&1 || die
rpm -ivh "$TEMPDIR/kernel-$DISTROVERSION.src.rpm" >> "$LOGFILE" 2>&1 || die
rpmbuild -bp "--target=$(uname -m)" "$HOME/rpmbuild/SPECS/kernel.spec" >> "$LOGFILE" 2>&1 ||
die "rpmbuild -bp failed. you may need to run 'yum-builddep kernel' first."
rm -rf "$CACHEDIR"
mkdir -p "$OBJDIR"
mv "$HOME"/rpmbuild/BUILD/kernel-*/linux-"$ARCHVERSION" "$SRCDIR" >> "$LOGFILE" 2>&1 || die
cp "$SRCDIR/.config" "$OBJDIR" || die
echo "$LOCALVERSION" > "$SRCDIR/localversion" || die
fi
echo "Testing patch file"
cd "$SRCDIR" || die
cp "$PATCHFILE" "$APPLIEDPATCHFILE" || die
patch -p1 < "$APPLIEDPATCHFILE" || die "source patch file failed to apply"
patch -p1 -R < "$APPLIEDPATCHFILE" &> /dev/null || die "reverse patch apply failed"
echo "Building original kernel"
make mrproper >> "$LOGFILE" 2>&1 || die
make "-j$CPUS" vmlinux "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die
cp -LR "$DATADIR/patch" "$TEMPDIR" || die
cp "$OBJDIR/vmlinux" "$TEMPDIR" || die
echo "Building patched kernel"
patch -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 || die
make "-j$CPUS" vmlinux "O=$OBJDIR" > "$TEMPDIR/patched_build.log" 2>&1 || die
echo "Detecting changed objects"
grep CC "$TEMPDIR/patched_build.log" | grep -v init/version.o | awk '{print $2}' > "$TEMPDIR/changed_objs"
[[ ! -s "$TEMPDIR/changed_objs" ]] && die "no changed objects were detected"
echo "Rebuilding changed objects"
rm -rf "$OBJDIR2"
mkdir -p "$OBJDIR2"
cp "$OBJDIR/.config" "$OBJDIR2" || die
mkdir "$TEMPDIR/patched"
for i in $(cat $TEMPDIR/changed_objs); do
KCFLAGS="-ffunction-sections -fdata-sections" make "$i" "O=$OBJDIR2" >> "$LOGFILE" 2>&1 || die
$STRIPCMD "$OBJDIR2/$i" >> "$LOGFILE" 2>&1 || die
mkdir -p "$TEMPDIR/patched/$(dirname $i)"
cp -f "$OBJDIR2/$i" "$TEMPDIR/patched/$i" || die
done
patch -R -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1
rm -f "$APPLIEDPATCHFILE"
mkdir "$TEMPDIR/orig"
for i in $(cat $TEMPDIR/changed_objs); do
rm -f "$i"
KCFLAGS="-ffunction-sections -fdata-sections" make "$i" "O=$OBJDIR2" >> "$LOGFILE" 2>&1 || die
$STRIPCMD -d "$OBJDIR2/$i" >> "$LOGFILE" 2>&1 || die
mkdir -p "$TEMPDIR/orig/$(dirname $i)"
cp -f "$OBJDIR2/$i" "$TEMPDIR/orig/$i" || die
done
echo "Extracting new and modified ELF sections"
cd "$TEMPDIR/orig"
FILES="$(find * -type f)"
cd "$TEMPDIR"
mkdir output
for i in $FILES; do
mkdir -p "output/$(dirname $i)"
"$TOOLSDIR"/create-diff-object "orig/$i" "patched/$i" "output/$i" 2>&1 |tee -a "$LOGFILE"
[[ "${PIPESTATUS[0]}" -eq 0 ]] || die
done
echo "Building patch module: kpatch-$PATCHNAME.ko"
cp "$OBJDIR/.config" "$SRCDIR"
cd "$SRCDIR"
make prepare >> "$LOGFILE" 2>&1 || die
cd "$TEMPDIR/output"
ld -r -o ../patch/output.o $FILES >> "$LOGFILE" 2>&1 || die
cd "$TEMPDIR/patch"
"$TOOLSDIR"/add-patches-section output.o ../vmlinux >> "$LOGFILE" 2>&1 || die
KPATCH_BUILD="$SRCDIR" KPATCH_NAME="$PATCHNAME" make "O=$OBJDIR" >> "$LOGFILE" 2>&1 || die
$STRIPCMD "kpatch-$PATCHNAME.ko" >> "$LOGFILE" 2>&1 || die
"$TOOLSDIR"/link-vmlinux-syms "kpatch-$PATCHNAME.ko" ../vmlinux >> "$LOGFILE" 2>&1 || die
cp -f "$TEMPDIR/patch/kpatch-$PATCHNAME.ko" "$BASE" || die
rm -f "$LOGFILE"
echo "SUCCESS"