From: Song Liu <song@kernel.org>
To: live-patching@vger.kernel.org, linux-kselftest@vger.kernel.org
Cc: jpoimboe@kernel.org, jikos@kernel.org, mbenes@suse.cz,
pmladek@suse.com, joe.lawrence@redhat.com, shuah@kernel.org,
linux-kernel@vger.kernel.org, Song Liu <song@kernel.org>
Subject: [PATCH v2 03/12] klp-build: Move patch processing to klp-prepare-patch
Date: Wed, 28 Oct 2026 14:45:25 -0700 [thread overview]
Message-ID: <20261028214534.1919398-4-song@kernel.org> (raw)
In-Reply-To: <20261028214534.1919398-1-song@kernel.org>
klp-build's patch processing doesn't need a kernel build, but the only
way to run it is through klp-build, which builds the kernel twice. That
makes it hard to test.
Move it into a new script, klp-prepare-patch, so that tests can run it
directly. It validates the patches, combines them into a single patch
against the source tree in the current directory, adds #line directives
and prints the result. klp-build runs it and builds the patched kernel
with its output.
klp-prepare-patch doesn't need a configured kernel tree, and it uses its
own temporary directory, so it leaves klp-tmp alone. The intermediate
patch without #line directives, which klp-build left in klp-tmp, now
goes away with that directory.
klp-build still applies and reverts the combined patch, so a little of
the patch handling is duplicated between the two scripts.
Suggested-by: Josh Poimboeuf <jpoimboe@kernel.org>
Assisted-by: Claude:claude-opus-5
Signed-off-by: Song Liu <song@kernel.org>
---
scripts/livepatch/Makefile | 3 +-
scripts/livepatch/klp-build | 106 +----------
scripts/livepatch/klp-prepare-patch | 277 ++++++++++++++++++++++++++++
3 files changed, 287 insertions(+), 99 deletions(-)
create mode 100755 scripts/livepatch/klp-prepare-patch
diff --git a/scripts/livepatch/Makefile b/scripts/livepatch/Makefile
index 17b590213740..d77ef3de7d9a 100644
--- a/scripts/livepatch/Makefile
+++ b/scripts/livepatch/Makefile
@@ -4,7 +4,8 @@
SHELLCHECK := $(shell which shellcheck 2> /dev/null)
SRCS := \
- klp-build
+ klp-build \
+ klp-prepare-patch
.DEFAULT_GOAL := help
.PHONY: help
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 28c3bf6b6418..4796b36b5cc8 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -31,7 +31,7 @@ KEEP_TMP=1
SCRIPT="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines"
+PREPARE_PATCH="$SCRIPT_DIR/klp-prepare-patch"
OBJTOOL="$PWD/tools/objtool/objtool"
CONFIG="$PWD/.config"
@@ -46,7 +46,6 @@ KMOD_DIR="$TMP_DIR/5-kmod"
STASH_DIR="$TMP_DIR/stash"
TIMESTAMP="$TMP_DIR/timestamp"
-PATCH_TMP_DIR="$TMP_DIR/tmp"
COMBINED_PATCH="$TMP_DIR/combined.patch"
KLP_DIFF_LOG="$DIFF_DIR/diff.log"
@@ -329,34 +328,6 @@ set_kernelversion() {
sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion
}
-get_patch_files() {
- local patch="$1"
-
- grep0 -E '^(---|\+\+\+) ' "$patch" \
- | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \
- | gawk '{print $2}' \
- | sed 's|^[^/]*/||' \
- | sort -u
-}
-
-check_unsupported_patches() {
- local patch
-
- for patch in "${PATCHES[@]}"; do
- local files=()
-
- get_patch_files "$patch" | mapfile -t files
-
- for file in "${files[@]}"; do
- case "$file" in
- lib/*|*/vdso/*|*/realmode/rm/*|*.S)
- die "${patch}: unsupported patch to $file"
- ;;
- esac
- done
- done
-}
-
apply_patch() {
local patch="$1"
shift
@@ -394,14 +365,6 @@ revert_patch() {
APPLIED_PATCHES=("${tmp[@]}")
}
-apply_patches() {
- local patch
-
- for patch in "${PATCHES[@]}"; do
- apply_patch "$patch"
- done
-}
-
revert_patches() {
local patches=("${APPLIED_PATCHES[@]}")
@@ -436,78 +399,25 @@ do_init() {
APPLIED_PATCHES=()
- [[ -x "$FIX_PATCH_LINES" ]] || die "can't find fix-patch-lines"
- command -v recountdiff &>/dev/null || die "recountdiff not found (install patchutils)"
+ [[ -x "$PREPARE_PATCH" ]] || die "can't find klp-prepare-patch"
validate_config
set_module_name
set_kernelversion
}
-# Combine the patches into a single patch against the original source. The
-# patches may depend on each other, so they can't be diffed individually.
-combine_patches() {
- local outfile="$1"
- local tmpdir="$PATCH_TMP_DIR"
- local files=()
- local patch
- local file
-
- rm -rf "$tmpdir"
- mkdir -p "$tmpdir"
-
- # Get all source files affected by the patches
- for patch in "${PATCHES[@]}"; do
- get_patch_files "$patch"
- done | sort -u | mapfile -t files
-
- # Copy orig source files to $tmpdir
- for file in "${files[@]}"; do
- if [[ -e "$file" ]]; then
- cp --parents "$file" "$tmpdir"
- fi
- done
-
- # Diff the orig source files against the patched ones in place to make a
- # clean patch. Use /dev/null for created and deleted files.
- : > "$outfile"
- apply_patches
- for file in "${files[@]}"; do
- local old="$tmpdir/$file"
- local new="$file"
- local old_label="a/$file"
- local new_label="b/$file"
- local status=0
-
- if [[ ! -e "$old" ]]; then
- old=/dev/null
- old_label=/dev/null
- fi
- if [[ ! -e "$new" ]]; then
- new=/dev/null
- new_label=/dev/null
- fi
-
- diff -up --label "$old_label" --label "$new_label" "$old" "$new" \
- >> "$outfile" || status=$?
- if (( status > 1 )); then
- die "diff failed for $file"
- fi
- done
- revert_patches
-}
-
# Validate the patches, combine them into $COMBINED_PATCH, and fix its lines so
# as not to affect the __LINE__ macro for otherwise unchanged functions further
# down the file.
process_patches() {
- local tmp_patch="$TMP_DIR/tmp.patch"
+ local cmd=()
- check_unsupported_patches
- combine_patches "$tmp_patch"
- [[ ! -s "$tmp_patch" ]] && die "no changes detected"
+ cmd=("$PREPARE_PATCH")
+ [[ -v VERBOSE ]] && cmd+=("--verbose")
+ cmd+=("--")
+ cmd+=("${PATCHES[@]}")
- "$FIX_PATCH_LINES" "$tmp_patch" | recountdiff > "$COMBINED_PATCH"
+ "${cmd[@]}" > "$COMBINED_PATCH" || die "klp-prepare-patch failed"
}
clean_kernel() {
diff --git a/scripts/livepatch/klp-prepare-patch b/scripts/livepatch/klp-prepare-patch
new file mode 100755
index 000000000000..f6f8524f7088
--- /dev/null
+++ b/scripts/livepatch/klp-prepare-patch
@@ -0,0 +1,277 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Validate the patches given to klp-build and combine them into a single patch
+
+if (( BASH_VERSINFO[0] < 4 || \
+ (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then
+ echo "error: this script requires bash 4.4+" >&2
+ exit 1
+fi
+
+set -o errtrace
+set -o pipefail
+set -o nounset
+
+# Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'.
+# This helps keep execution in pipes so pipefail+ERR trap can catch errors.
+shopt -s lastpipe
+
+unset TMP_DIR VERBOSE
+
+SCRIPT="$(basename "$0")"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines"
+
+APPLIED_PATCHES=()
+
+grep0() {
+ # shellcheck disable=SC2317
+ command grep "$@" || true
+}
+
+# Because pipefail is enabled, the grep0 helper should be used instead of
+# grep, otherwise a failed match can propagate to an error.
+grep() {
+ echo "error: $SCRIPT: use grep0 or 'command grep' instead of bare grep" >&2
+ exit 1
+}
+
+warn() {
+ echo "warning: $SCRIPT: $*" >&2
+}
+
+die() {
+ echo "error: $SCRIPT: $*" >&2
+ exit 1
+}
+
+cleanup() {
+ set +o nounset
+ revert_patches
+ [[ -v TMP_DIR ]] && rm -rf "$TMP_DIR"
+ return 0
+}
+
+trap_err() {
+ die "line ${BASH_LINENO[0]}: '$BASH_COMMAND'"
+}
+
+trap cleanup EXIT INT TERM HUP
+trap trap_err ERR
+
+usage() {
+ cat <<EOF >&2
+Usage: $SCRIPT [OPTIONS] PATCH_FILE(s)
+Validate patches and combine them into a single patch for klp-build.
+
+The patches are applied in order to the source tree in the current directory,
+and reverted afterwards. The combined patch is printed to stdout, with #line
+directives added so that the patch doesn't change the line numbers of the code
+after the changed lines.
+
+Options:
+ -v, --verbose Show the details of patches which apply with fuzz
+
+EOF
+}
+
+process_args() {
+ local short
+ local long
+ local args
+ local patch
+
+ short="hv"
+ long="help,verbose"
+
+ args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
+ echo; usage; exit 1
+ }
+ eval set -- "$args"
+
+ while true; do
+ case "$1" in
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ -v | --verbose)
+ VERBOSE=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ if [[ $# -eq 0 ]]; then
+ usage
+ exit 1
+ fi
+
+ PATCHES=("$@")
+
+ for patch in "${PATCHES[@]}"; do
+ [[ -f "$patch" ]] || die "$patch doesn't exist"
+ done
+}
+
+do_init() {
+ [[ -x "$FIX_PATCH_LINES" ]] || die "can't find fix-patch-lines"
+ command -v recountdiff &>/dev/null || die "recountdiff not found (install patchutils)"
+
+ TMP_DIR="$(mktemp -d)"
+}
+
+get_patch_files() {
+ local patch="$1"
+
+ grep0 -E '^(---|\+\+\+) ' "$patch" \
+ | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \
+ | gawk '{print $2}' \
+ | sed 's|^[^/]*/||' \
+ | sort -u
+}
+
+check_unsupported_patches() {
+ local patch
+
+ for patch in "${PATCHES[@]}"; do
+ local files=()
+
+ get_patch_files "$patch" | mapfile -t files
+
+ for file in "${files[@]}"; do
+ case "$file" in
+ lib/*|*/vdso/*|*/realmode/rm/*|*.S)
+ die "${patch}: unsupported patch to $file"
+ ;;
+ esac
+ done
+ done
+}
+
+apply_patch() {
+ local patch="$1"
+ local drift_regex="with fuzz|offset [0-9]+ line"
+ local output
+ local status
+
+ status=0
+ output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null < "$patch" 2>&1) || status=$?
+ if [[ "$status" -ne 0 ]]; then
+ echo "$output" >&2
+ die "$patch did not apply"
+ elif [[ "$output" =~ $drift_regex ]]; then
+ [[ -v VERBOSE ]] && echo "$output" >&2
+ warn "${patch} applied with fuzz"
+ fi
+
+ APPLIED_PATCHES+=("$patch")
+ patch -p1 --no-backup-if-mismatch -r /dev/null --silent < "$patch"
+}
+
+revert_patch() {
+ local patch="$1"
+ local tmp=()
+
+ patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true
+
+ for p in "${APPLIED_PATCHES[@]}"; do
+ [[ "$p" == "$patch" ]] && continue
+ tmp+=("$p")
+ done
+
+ APPLIED_PATCHES=("${tmp[@]}")
+}
+
+apply_patches() {
+ local patch
+
+ for patch in "${PATCHES[@]}"; do
+ apply_patch "$patch"
+ done
+}
+
+revert_patches() {
+ local patches=("${APPLIED_PATCHES[@]}")
+
+ for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do
+ revert_patch "${patches[$i]}"
+ done
+
+ APPLIED_PATCHES=()
+}
+
+# Combine the patches into a single patch against the original source. The
+# patches may depend on each other, so they can't be diffed individually.
+combine_patches() {
+ local outfile="$1"
+ local orig_dir="$TMP_DIR/orig"
+ local files=()
+ local patch
+ local file
+
+ mkdir -p "$orig_dir"
+
+ # Get all source files affected by the patches
+ for patch in "${PATCHES[@]}"; do
+ get_patch_files "$patch"
+ done | sort -u | mapfile -t files
+
+ # Copy orig source files to $orig_dir
+ for file in "${files[@]}"; do
+ if [[ -e "$file" ]]; then
+ cp --parents "$file" "$orig_dir"
+ fi
+ done
+
+ # Diff the orig source files against the patched ones in place to make a
+ # clean patch. Use /dev/null for created and deleted files.
+ : > "$outfile"
+ apply_patches
+ for file in "${files[@]}"; do
+ local old="$orig_dir/$file"
+ local new="$file"
+ local old_label="a/$file"
+ local new_label="b/$file"
+ local status=0
+
+ if [[ ! -e "$old" ]]; then
+ old=/dev/null
+ old_label=/dev/null
+ fi
+ if [[ ! -e "$new" ]]; then
+ new=/dev/null
+ new_label=/dev/null
+ fi
+
+ diff -up --label "$old_label" --label "$new_label" "$old" "$new" \
+ >> "$outfile" || status=$?
+ if (( status > 1 )); then
+ die "diff failed for $file"
+ fi
+ done
+ revert_patches
+}
+
+
+################################################################################
+
+process_args "$@"
+do_init
+
+check_unsupported_patches
+combine_patches "$TMP_DIR/combined.patch"
+[[ ! -s "$TMP_DIR/combined.patch" ]] && die "no changes detected"
+
+# Fix the patch lines so as not to affect the __LINE__ macro for otherwise
+# unchanged functions further down the file.
+"$FIX_PATCH_LINES" "$TMP_DIR/combined.patch" | recountdiff
--
2.53.0-Meta
next prev parent reply other threads:[~2026-10-28 21:45 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 21:45 [PATCH v2 00/12] klp-build: Fix dependent patches, add selftests Song Liu
2026-10-28 21:45 ` [PATCH v2 01/12] klp-build: Fix dependent patches in a series Song Liu
2026-10-28 21:45 ` [PATCH v2 02/12] klp-build: Process the patches before building the original kernel Song Liu
2026-10-28 21:45 ` Song Liu [this message]
2026-10-28 21:45 ` [PATCH v2 04/12] selftests/livepatch: Add tests for klp-prepare-patch Song Liu
2026-10-28 21:45 ` [PATCH v2 05/12] selftests/livepatch: Test klp-prepare-patch with dependent patches Song Liu
2026-10-28 21:45 ` [PATCH v2 06/12] selftests/livepatch: Test klp-prepare-patch with independent patches Song Liu
2026-10-28 21:45 ` [PATCH v2 07/12] selftests/livepatch: Test klp-prepare-patch with patches which cancel out Song Liu
2026-10-28 21:45 ` [PATCH v2 08/12] selftests/livepatch: Test klp-prepare-patch with created and deleted files Song Liu
2026-10-28 21:45 ` [PATCH v2 09/12] selftests/livepatch: Test where klp-prepare-patch adds #line directives Song Liu
2026-10-28 21:45 ` [PATCH v2 10/12] selftests/livepatch: Test klp-prepare-patch with unsupported files Song Liu
2026-10-28 21:45 ` [PATCH v2 11/12] selftests/livepatch: Test klp-prepare-patch with patches which don't apply Song Liu
2026-10-28 21:45 ` [PATCH v2 12/12] selftests/livepatch: Test klp-prepare-patch with offset and fuzz Song Liu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261028214534.1919398-4-song@kernel.org \
--to=song@kernel.org \
--cc=jikos@kernel.org \
--cc=joe.lawrence@redhat.com \
--cc=jpoimboe@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=live-patching@vger.kernel.org \
--cc=mbenes@suse.cz \
--cc=pmladek@suse.com \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox