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.24.1.5 $ # # 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 #PATCHID# 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 #PATCHID# patch"
<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 | head -1 2>/dev/null | \ sed -e 's/EXTRAVERSION =[ ]*\([^ ]*\)$/\1/') KERNELBRANCHLET=${VERSION}.${PATCHLEVEL}.${SUBLEVEL} KERNELRELEASE=${KERNELBRANCHLET}${EXTRAVERSION}
The candidates patch version for application to our kernel (eg. 2.6.11.11) is the first of:
<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" = "$KERNELBRANCHLET" -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}/${KERNELBRANCHLET}/apply/$dep ] then NEEDED_DEPS="${ARCHITECTURE}/${KERNELBRANCHLET}/apply/$dep $NEEDED_DEPS" elif [ -x ${TOPPATCHDIR}/all/${KERNELBRANCHLET}/apply/$dep ] then NEEDED_DEPS="all/${KERNELBRANCHLET}/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 #PATCHID# patch" 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 dep=$(basename $apply) ${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 #PATCHID# patch" 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 #PATCHID# patch" 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#'