The klp-build script is currently very strict with input patches,
requiring them to apply cleanly via `git apply --recount`.  This
prevents the use of patches with minor contextual fuzz relative to the
target kernel sources.

Add an optional -z/--fuzz option to allow klp-build to "rebase" input
patches within its klp-tmp/ scratch space.  When enabled, the script
utilizes GNU patch's fuzzy matching to apply changes to a temporary
directory and then creates a normalized version of the patch using `git
diff --no-index`.

This rebased patch contains the exact line counts and context required
for the subsequent klp-build fixup and build steps, allowing users to
reuse a patch across similar kernel streams.

Signed-off-by: Joe Lawrence <[email protected]>
---
 scripts/livepatch/klp-build | 105 +++++++++++++++++++++++++++++++++++-
 1 file changed, 103 insertions(+), 2 deletions(-)

Using the same 1-line-offset input combined.patch from the previous
patch in this set and adding --fuzz, we can successfully now build it:

  $ ./scripts/livepatch/klp-build -T --fuzz combined.patch
  Rebasing 1 patch(es)
  -> combined.patch
  patching file fs/proc/cmdline.c
  Hunk #1 succeeded at 7 (offset 1 line).
  patching file fs/proc/version.c
  patching file fs/proc/cmdline.c
  Hunk #1 succeeded at 7 (offset 1 line).
  patching file fs/proc/version.c
  Validating patch(es)
  Building original kernel
  Copying original object files
  Fixing patch(es)
  Building patched kernel
  Copying patched object files
  Diffing objects
  vmlinux.o: changed function: cmdline_proc_show
  vmlinux.o: changed function: version_proc_show
  Building patch module: livepatch-combined.ko
  SUCCESS 

diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 2313bc909f58..535ca18e32c5 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -26,6 +26,7 @@ REPLACE=1
 SHORT_CIRCUIT=0
 JOBS="$(getconf _NPROCESSORS_ONLN)"
 VERBOSE="-s"
+FUZZ_FACTOR=""
 shopt -o xtrace | grep -q 'on' && XTRACE=1
 
 # Avoid removing the previous $TMP_DIR until args have been fully processed.
@@ -49,6 +50,7 @@ KMOD_DIR="$TMP_DIR/kmod"
 STASH_DIR="$TMP_DIR/stash"
 TIMESTAMP="$TMP_DIR/timestamp"
 PATCH_TMP_DIR="$TMP_DIR/tmp"
+REBASE_DIR="$TMP_DIR/rebase"
 
 KLP_DIFF_LOG="$DIFF_DIR/diff.log"
 
@@ -131,6 +133,7 @@ Advanced Options:
                                   3|diff       Diff objects
                                   4|kmod       Build patch module
    -T, --keep-tmp              Preserve tmp dir on exit
+   -z, --fuzz[=NUM]            Rebase patches using fuzzy matching [default: 2]
 
 EOF
 }
@@ -145,8 +148,8 @@ process_args() {
        local long
        local args
 
-       short="hfj:o:vdS:T"
-       
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+       short="hfj:o:vdS:Tz::"
+       
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp,fuzz::"
 
        args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
                echo; usage; exit
@@ -204,6 +207,14 @@ process_args() {
                                keep_tmp=1
                                shift
                                ;;
+                       -z | --fuzz)
+                               if [[ -n "$2" ]]; then
+                                       FUZZ_FACTOR="$2"
+                               else
+                                       FUZZ_FACTOR=2
+                               fi
+                               shift 2
+                               ;;
                        --)
                                shift
                                break
@@ -304,6 +315,94 @@ get_patch_files() {
                | sort -u
 }
 
+# Rebase a patch using GNU patch with fuzz
+# Outputs path to rebased patch on success, non-zero on failure
+rebase_patch() {
+       local idx="$1"
+       local input_patch="$2"
+       local patch_name="$(basename "$input_patch" .patch)"
+       local work_dir="$REBASE_DIR/$idx-$patch_name"
+       local output_patch="$work_dir/rebased.patch"
+       local files=()
+       local file
+
+       rm -rf "$work_dir"
+       mkdir -p "$work_dir/orig" "$work_dir/patched"
+
+       get_patch_files "$input_patch" | mapfile -t files
+
+       # Copy original files (before patch)
+       for file in "${files[@]}"; do
+               [[ "$file" == "dev/null" ]] && continue
+               if [[ -f "$SRC/$file" ]]; then
+                       mkdir -p "$work_dir/orig/$(dirname "$file")"
+                       cp -f "$SRC/$file" "$work_dir/orig/$file"
+               fi
+       done
+
+       # Apply with fuzz
+       (
+               cd "$SRC"
+               sed -n '/^-- /q;p' "$input_patch" | \
+                       patch -p1 \
+                               -F"$FUZZ_FACTOR" \
+                               --no-backup-if-mismatch \
+                               -r /dev/null \
+                               --forward >&2
+       ) || return 1
+
+       # Copy patched files (after patch)
+       for file in "${files[@]}"; do
+               [[ "$file" == "dev/null" ]] && continue
+               if [[ -f "$SRC/$file" ]]; then
+                       mkdir -p "$work_dir/patched/$(dirname "$file")"
+                       cp -f "$SRC/$file" "$work_dir/patched/$file"
+               fi
+       done
+
+       # Revert with fuzz
+       (
+               cd "$SRC"
+               sed -n '/^-- /q;p' "$input_patch" | \
+                       patch -p1 -R \
+                               -F"$FUZZ_FACTOR" \
+                               --no-backup-if-mismatch \
+                               -r /dev/null >&2
+       ) || {
+               warn "fuzzy revert failed; source tree may be corrupted"
+               return 1
+       }
+
+       # Generate clean patch from captured state
+       ( cd "$work_dir" && git diff --no-index --no-prefix orig patched ) > 
"$output_patch" || true
+
+       echo "$output_patch"
+}
+
+# If the user specified --fuzz, iterate through PATCHES and rebase them
+# Updates PATCHES array in-place with rebased patch paths
+maybe_rebase_patches() {
+       local i
+       local idx
+       local patch
+       local rebased
+
+       [[ -z "$FUZZ_FACTOR" ]] && return 0
+
+       status "Rebasing ${#PATCHES[@]} patch(es)"
+
+       mkdir -p "$REBASE_DIR"
+
+       idx=0001
+       for i in "${!PATCHES[@]}"; do
+               patch="${PATCHES[$i]}"
+               echo "-> $(basename "$patch")"
+               rebased=$(rebase_patch "$idx" "$patch") || die "rebase failed: 
$patch"
+               PATCHES[i]="$rebased"
+               idx=$(printf "%04d" $(( 10#$idx + 1 )))
+       done
+}
+
 # Make sure git re-stats the changed files
 git_refresh() {
        local patch="$1"
@@ -807,6 +906,8 @@ build_patch_module() {
 process_args "$@"
 do_init
 
+maybe_rebase_patches
+
 if (( SHORT_CIRCUIT <= 1 )); then
        status "Validating patch(es)"
        validate_patches
-- 
2.52.0


Reply via email to