mirror of
https://github.com/dynup/kpatch
synced 2025-05-06 01:47:58 +00:00
Create the applied-patch file only after the patch has been verified. Otherwise if you accidentally supply a patch which had already been applied to the source, the cleanup trap won't reverse apply it when exiting the script.
257 lines
8.3 KiB
Bash
Executable File
257 lines
8.3 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"
|
|
DEBUG=0
|
|
|
|
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() {
|
|
[[ "$DEBUG" -eq 0 ]] && rm -rf "$TEMPDIR"
|
|
if [[ -e "$SRCDIR/$APPLIEDPATCHFILE" ]]; then
|
|
patch -p1 -R -d "$SRCDIR" < "$SRCDIR/$APPLIEDPATCHFILE" &> /dev/null
|
|
rm -f "$SRCDIR/$APPLIEDPATCHFILE"
|
|
fi
|
|
}
|
|
|
|
find_dirs() {
|
|
if [[ -e "$SCRIPTDIR/create-diff-object" ]]; then
|
|
# git repo
|
|
TOOLSDIR="$SCRIPTDIR"
|
|
DATADIR="$(readlink -f $SCRIPTDIR/../kmod)"
|
|
SYMVERSFILE="$DATADIR/core/Module.symvers"
|
|
return
|
|
elif [[ -e "$SCRIPTDIR/../libexec/kpatch/create-diff-object" ]]; then
|
|
# installation path
|
|
TOOLSDIR="$(readlink -f $SCRIPTDIR/../libexec/kpatch)"
|
|
DATADIR="$(readlink -f $SCRIPTDIR/../share/kpatch)"
|
|
SYMVERSFILE="$(readlink -f $SCRIPTDIR/../lib/modules/$(uname -r)/kpatch/Module.symvers)"
|
|
return
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
usage() {
|
|
echo "usage: $0 [options] <patch file>" >&2
|
|
echo " -h, --help Show this help message" >&2
|
|
echo " -s, --sourcedir Specify kernel source directory" >&2
|
|
echo " -c, --config Specify kernel config file" >&2
|
|
echo " -d, --debug Keep scratch files in /tmp" >&2
|
|
}
|
|
|
|
PARSED_OPT_ARRAY=($(getopt -u -n "$0" -o hs:c:d -l "help,sourcedir:,config:,debug" -- "$@")) || die "getopt failed"
|
|
|
|
for index in ${!PARSED_OPT_ARRAY[*]}; do
|
|
case "${PARSED_OPT_ARRAY[$index]}" in
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-s|--sourcedir)
|
|
USERSRCDIR="$(readlink -f ${PARSED_OPT_ARRAY[$(( $index+1 ))]})"
|
|
[[ ! -d "$USERSRCDIR" ]] && die "source dir $USERSRCDIR not found"
|
|
;;
|
|
-c|--config)
|
|
CONFIGFILE="$(readlink -f ${PARSED_OPT_ARRAY[$(( $index+1 ))]})"
|
|
[[ ! -f "$CONFIGFILE" ]] && die "config file $CONFIGFILE not found"
|
|
;;
|
|
-d|--debug)
|
|
echo "DEBUG mode enabled"
|
|
DEBUG=1
|
|
;;
|
|
--)
|
|
PATCHFILE="$(readlink -f ${PARSED_OPT_ARRAY[$(( $index+1 ))]})"
|
|
[[ ! -f "$PATCHFILE" ]] && die "patch file $PATCHFILE not found"
|
|
break
|
|
;;
|
|
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_dirs || die "can't find supporting tools"
|
|
|
|
[[ -e "$SYMVERSFILE" ]] || die "can't find core module Module.symvers"
|
|
|
|
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"
|
|
|
|
if [[ -n "$CONFIGFILE" ]]; then
|
|
cp "$CONFIGFILE" "$OBJDIR/.config" || die "config file is missing"
|
|
else
|
|
cp "$USERSRCDIR/.config" "$OBJDIR" || die "source dir is missing a .config file"
|
|
fi
|
|
|
|
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
|
|
patch -N -p1 < "$PATCHFILE" || die "source patch file failed to apply"
|
|
cp "$PATCHFILE" "$APPLIEDPATCHFILE" || die
|
|
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 -N -p1 < "$APPLIEDPATCHFILE" >> "$LOGFILE" 2>&1 || die
|
|
make "-j$CPUS" vmlinux "O=$OBJDIR" 2>&1 | tee -a "$TEMPDIR/patched_build.log" >> "$LOGFILE"
|
|
[[ "${PIPESTATUS[0]}" -eq 0 ]] || die
|
|
|
|
echo "Detecting changed objects"
|
|
grep CC "$TEMPDIR/patched_build.log" | grep -v -e init/version.o -e scripts/mod/devicetable-offsets.s -e scripts/mod/file2alias.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" KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE" 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"
|