All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.