The dh-kpatches system is made of two parts. The first one takes patch descriptions, and produces kernel-patch packages; the second one is (currently) contained in those patch packages, and handles application of the patches to a kernel source tree, as well as their removal.
The application/removal scripts are (currently) generated from templates by the dh_installkpatches script for each kernel-patch package.
Currently, the patch descriptions are completely parsed by dh_installkpatches, and the apply/unpatch scripts are specific to each package, and can only act on the patches dh_installkpatches taught them about.
This is the debhelper-based script that will cause our packages to install patches in the One True Way. Its base structure is that of a standard debhelper script.
<dh_installkpatches.in>=
#!/usr/bin/perl -w
#
# dh_installkpatches $Revision: 1.17.1.4.1.23 $
#
# Reads debian/$package.kpatches[.foo], installs all files necessary
# to have make-kpkg use the kernel patches described in there.
#
# (c) 2000-2003 Yann Dirson <dirson@debian.org>
# Some parts based on code from Adam Di Carlo and Joey Hess
use strict;
use Debian::Debhelper::Dh_Lib;
init();
<definitions for the core kpatch system>
PACKAGE: foreach my $package (@{$dh{DOPACKAGES}}) {
my $tmp = tmpdir($package);
my $ext = pkgext($package);
# There are two filename formats, the usual
# plus an extended format (debian/package.*).
opendir(DEB,"debian/") || error("can't read debian directory: $!");
# If this is the main package, we need to handle unprefixed filenames.
# For all packages, we must support both the usual filename format plus
# that format with a period an something appended.
my $regexp="\Q$package\E\.";
if ($package eq $dh{MAINPACKAGE}) {
$regexp="(|$regexp)";
}
my @files = grep { /^${regexp}kpatches(\..*)?$/ } readdir(DEB);
closedir(DEB);
# next package if there are no patches in there
next PACKAGE if $#files < 0;
<process binary package $package>
}
For each binary package, all kpatches files are processed to generate one apply script with a single diff operation (and its corresponding unpatch script).
<process binary package $package>= (<-U)
<assert kpatch:depends is referenced in control file>
foreach my $file (@files) {
my %patchinfo = read_control_file ("debian/$file");
# use Data::Dumper;
# print Dumper (%patchinfo);
my $patchid = $patchinfo{general}->{'patch-id'};
# transformation of the ID to be acceptable as part of an envvar's name
$patchinfo{general}->{'clean-patch-id'} = $patchinfo{general}->{'patch-id'};
$patchinfo{general}->{'clean-patch-id'} =~ s/-/_/g;
# protect pipes and dquotes for sed command-line
$patchinfo{general}->{'patch-name'} =~ s,([|\"]),\\$1,g;
<generate apply/unpatch scripts for given $package and kpatches $file>
}
<set kpatch:Depends substvar>
A number of packages are needed by kernel-patch packages:
<definitions for the core kpatch system>= (<-U) [D->] my $pkgdeps = "bash (>= 2.0), patch, grep-dctrl";
The substvar code was derived from similar functionnality in dh_perl v3.4.1. For idempotency, we first remove anything this program might have previously added to the substvars file.
<set kpatch:Depends substvar>= (<-U)
if (-e "debian/${ext}substvars") {
complex_doit("grep -v ^kpatch:Depends= debian/${ext}substvars > debian/${ext}substvars.new || true");
doit("mv", "debian/${ext}substvars.new","debian/${ext}substvars");
}
complex_doit("echo 'kpatch:Depends=$pkgdeps' >> debian/${ext}substvars");
We also make sure the package uses our substvar, and abort if not.
<assert kpatch:depends is referenced in control file>= (<-U)
die 'debian/control must make package ' . $package . ' depend on ${kpatch:Depends}'
if system ("dpkg-gencontrol -p$package -Pdebian -O -T/dev/null -Vkpatch:Depends=KPATCHISUSED |"
. "grep -q '^Depends: .*KPATCHISUSED'") != 0;
For each v0 kpatches file, we produce an apply and an unpatch script, necessary to work properly with make-kpkg. Those are currently produced from templates.
<apply.tmpl>= #! /bin/bash set -e <Assert this is a kernel tree> <Apply.tmpl constants> DEPENDS=(#DEPENDS#) KVERSIONS=(#KVERSIONS#) PATCHFILES=(#PATCHFILES#) DEBPATCHFILES=(#DEBPATCHFILES#) STRIPLEVELS=(#STRIPLEVELS#) <Make apply idempotent> <Get current kernel version> <Select alternative to apply, as $IDX> echo >&2 "START applying patch \"#PATCHNAME#\"" <Handle dependencies> <Assert patch applies, using dry-run> <Apply the patch> <Store necessary info for unpatching> <Create script to have this patch registered in doc-dir> echo >&2 "END applying patch \"#PATCHNAME#\""
<Get current kernel version>= (<-U)
VERSION=$(grep ^VERSION Makefile 2>/dev/null | \
sed -e 's/[^0-9]*\([0-9]*\)/\1/')
PATCHLEVEL=$( grep ^PATCHLEVEL Makefile 2>/dev/null | \
sed -e 's/[^0-9]*\([0-9]*\)/\1/')
SUBLEVEL=$(grep ^SUBLEVEL Makefile 2>/dev/null | \
sed -e 's/[^0-9]*\([0-9]*\)/\1/')
EXTRAVERSION=$(grep ^EXTRAVERSION Makefile 2>/dev/null | \
sed -e 's/EXTRAVERSION =[ ]*\([^ ]*\)$/\1/')
KERNELRELEASE=${VERSION}.${PATCHLEVEL}.${SUBLEVEL}${EXTRAVERSION}
<Select alternative to apply, as $IDX>= (<-U)
IDX=
declare -i i=${#PATCHFILES[*]}-1
while [ $i -ge 0 ]
do
v=${KVERSIONS[$i]}
if [ -n "$KPATCH_#CLEANPATCHID#" -a "$v" = "$KPATCH_#CLEANPATCHID#" \
-o "$v" = "$KERNELRELEASE" -o "$v" = all ]
then
IDX=$i
fi
i=i-1
done
<Assert we have an alternative to apply>
<Record parameters for applying alternative $IDX>
If the KPATCH_* mechanism was used, we have a special error message listing the requested version.
<Assert we have an alternative to apply>= (<-U)
if [ -n "$KPATCH_#CLEANPATCHID#" -a ${KVERSIONS[$IDX]} != "$KPATCH_#CLEANPATCHID#" ]
then
echo >&2 "Requested kernel version \`$KPATCH_#CLEANPATCHID#' not found for patch #PATCHID#"
exit 1
elif [ -z "$IDX" ]
then
echo >&2 "No \"#PATCHNAME#\" patch found for kernel version $KERNELRELEASE"
exit 1
fi
Of special notice is the "debian" pseudo-flavour. Before the kernel-patch-debian era, we had to check for README.Debian. Now we must check for version.Debian.
<Record parameters for applying alternative $IDX>= (<-U)
KVERSION=${KVERSIONS[$IDX]}
STRIPLEVEL=${STRIPLEVELS[$IDX]}
if [ "${DEBPATCHFILES[$IDX]}" != '' -a \
\( -r version.Debian -o -r README.Debian \) ]
then
PATCHFILE=${DEBPATCHFILES[$IDX]}
else
PATCHFILE=${PATCHFILES[$IDX]}
fi
<Handle dependencies>= (<-U) <Check for dependencies> <Apply dependencies>
<Check for dependencies>= (<-U)
NEEDED_DEPS=
for dep in ${DEPENDS[$IDX]}
do
if [ -x ${TOPPATCHDIR}/${ARCHITECTURE}/${VERSION}.${PATCHLEVEL}.${SUBLEVEL}/apply/$dep ]
then
NEEDED_DEPS="${ARCHITECTURE}/${VERSION}.${PATCHLEVEL}.${SUBLEVEL}/apply/$dep $NEEDED_DEPS"
elif [ -x ${TOPPATCHDIR}/all/${VERSION}.${PATCHLEVEL}.${SUBLEVEL}/apply/$dep ]
then
NEEDED_DEPS="all/${VERSION}.${PATCHLEVEL}.${SUBLEVEL}/apply/$dep $NEEDED_DEPS"
elif [ -x ${TOPPATCHDIR}/${ARCHITECTURE}/apply/$dep ]
then
NEEDED_DEPS="${ARCHITECTURE}/apply/$dep $NEEDED_DEPS"
elif [ -x ${TOPPATCHDIR}/all/apply/$dep ]
then
NEEDED_DEPS="all/apply/$dep $NEEDED_DEPS"
else
echo >&2 "ERROR: Patch dependency \`$dep' not found - aborting"
echo >&2 "END applying patch \"#PATCHNAME#\""
exit 1
fi
done
<Apply dependencies>= (<-U)
if [ "$NEEDED_DEPS" ]
then
echo >&2 "Ensuring the following patches are applied first: $NEEDED_DEPS"
for apply in ${NEEDED_DEPS}
do
${TOPPATCHDIR}/$apply
# check something was applied
if [ ! -f debian/APPLIED_${ARCHITECTURE}_$dep -a \
! -f debian/APPLIED_all_$dep ]
then
echo >&2 "ERROR: patch dependency did not left a patch stamp (version mismatch ?) - aborting"
echo >&2 "END applying patch \"#PATCHNAME#\""
exit 1
fi
done
UNPATCHDEPS=$(echo ${NEEDED_DEPS} | sed s,/apply/,/unpatch/,g)
fi
<Assert patch applies, using dry-run>= (<-U)
echo >&2 "Testing whether \"#PATCHNAME#\" patch for $KVERSION applies (dry run):"
if ! [ -r $PATCHFILE ]
then
echo >&2 "\"#PATCHNAME#\" patch for $KVERSION not found"
exit 1
elif ! $DECOMPRESSOR $PATCHFILE |
patch --force --dry-run $PATCH_OPTIONS -p$STRIPLEVEL
then
echo >&2 "\"#PATCHNAME#\" patch for $KVERSION does not apply cleanly"
exit 1
fi
We do not use --force on second run, there should be no need for it. If something requires interaction, it is likely there is a bug somewhere, better catch it.
After applying the diff, we remove any empty files, so that files "removed" by the diff are really removed. We make an exception for ./debian/ contents, and for files named APPLIED*, or else some (buggy) stamp files may be caught too.
<Apply the patch>= (<-U)
if ! $DECOMPRESSOR $PATCHFILE |
patch $PATCH_OPTIONS -p$STRIPLEVEL
then
# This should never happen, thanks to the dry-run
echo >&2 "ASSERTION FAILED - \"#PATCHNAME#\" patch for $KVERSION failed"
echo >&2 "END applying patch \"#PATCHNAME#\""
exit 1
fi
echo >&2 "\"#PATCHNAME#\" patch for $KVERSION succeeded"
<Remove empty files>
All information necessary for unpatching is stored in a debian/APPLIED_* file, for use by the unpatch script.
<Store necessary info for unpatching>= (<-U) mkdir -p debian cat > 'debian/APPLIED_#PATCHARCH#_#PATCHID#' <<EOF PATCHFILE='$PATCHFILE' STRIPLEVEL='$STRIPLEVEL' DEPENDS='$UNPATCHDEPS' EOF
<Create script to have this patch registered in doc-dir>= (<-U)
mkdir -p debian/image.d
PKGNAME=`dpkg -S $PATCHFILE | cut -d: -f1`
PKGVERS=`grep-dctrl -n -P $PKGNAME -s Version -X /var/lib/dpkg/status`
cat > 'debian/image.d/register-#PATCHID#' <<EOF
#!/bin/sh
# This scripts documents the "#PATCHNAME#" kernel patch into the
# kernel-image package, as being applied to the kernel.
docdir=\${IMAGE_TOP}/usr/share/doc/kernel-image-\${version}
mkdir -p \${docdir}
(
printf '#PATCHNAME# (#PATCHID#)${KPATCH_#CLEANPATCHID#:+ for kernel ${KPATCH_#CLEANPATCHID#}},'
echo ' from package $PKGNAME, version $PKGVERS'
) >> \${docdir}/applied-patches
EOF
chmod +x 'debian/image.d/register-#PATCHID#'