From 757bd10ff0f015d83481d39a266eb752dbbfce33 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:42 -0400 Subject: livepatch/klp-build: support patches that add/remove files The klp-build script prepares a clean patch by populating two temporary directories ('a' and 'b') with source files and diffing the result. However, this process fails when a patch introduces a new source file, as the script attempts to copy files that do not yet exist in the original source tree. Likewise, it fails when a patch removes a source file and the script attempts to copy a file that no longer exists. Refactor the file-gathering logic to distinguish between original input files and patched output files: - Split get_patch_files() into get_patch_input_files() and get_patch_output_files() to identify which files exist before and after patch application. - Filter out "/dev/null" from both to handle file creation/deletion. - Update refresh_patch() to only copy existing input files to the 'a' directory and the resulting output files to the 'b' directory. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-4-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 809e198a561d..94ed3b4a91d8 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -296,15 +296,33 @@ set_kernelversion() { sed -i "2i echo $localversion; exit 0" scripts/setlocalversion } -get_patch_files() { +get_patch_input_files() { + local patch="$1" + + grep0 -E '^--- ' "$patch" \ + | gawk '{print $2}' \ + | grep0 -v '^/dev/null$' \ + | sed 's|^[^/]*/||' \ + | sort -u +} + +get_patch_output_files() { local patch="$1" - grep0 -E '^(--- |\+\+\+ )' "$patch" \ + grep0 -E '^\+\+\+ ' "$patch" \ | gawk '{print $2}' \ + | grep0 -v '^/dev/null$' \ | sed 's|^[^/]*/||' \ | sort -u } +get_patch_files() { + local patch="$1" + + { get_patch_input_files "$patch"; get_patch_output_files "$patch"; } \ + | sort -u +} + # Make sure git re-stats the changed files git_refresh() { local patch="$1" @@ -312,7 +330,7 @@ git_refresh() { [[ ! -e "$SRC/.git" ]] && return - get_patch_files "$patch" | mapfile -t files + get_patch_input_files "$patch" | mapfile -t files ( cd "$SRC" @@ -426,21 +444,23 @@ do_init() { refresh_patch() { local patch="$1" local tmpdir="$PATCH_TMP_DIR" - local files=() + local input_files=() + local output_files=() rm -rf "$tmpdir" mkdir -p "$tmpdir/a" mkdir -p "$tmpdir/b" # Get all source files affected by the patch - get_patch_files "$patch" | mapfile -t files + get_patch_input_files "$patch" | mapfile -t input_files + get_patch_output_files "$patch" | mapfile -t output_files # Copy orig source files to 'a' - ( cd "$SRC" && echo "${files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) + ( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) # Copy patched source files to 'b' apply_patch "$patch" --recount - ( cd "$SRC" && echo "${files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) + ( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) revert_patch "$patch" --recount # Diff 'a' and 'b' to make a clean patch -- cgit v1.2.3 From d36a7343f4bac518b6ef05e2ccc47acd3a2cdab9 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:43 -0400 Subject: livepatch/klp-build: switch to GNU patch and recountdiff 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. To allow users to reuse a patch across similar kernel streams, switch to using GNU patch and patchutils for intermediate patch manipulation. Update the logic for applying, reverting, and regenerating patches: - Use 'patch -p1' for better handling of context fuzz. - Use 'recountdiff' to update line counts after FIX_PATCH_LINES. - Drop git_refresh() and related git-specific logic. Signed-off-by: Joe Lawrence Acked-by: Song Liu Link: https://patch.msgid.link/20260310203751.1479229-5-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 59 ++++++++++----------------------------------- 1 file changed, 13 insertions(+), 46 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 94ed3b4a91d8..564985a1588a 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -95,7 +95,7 @@ restore_files() { cleanup() { set +o nounset - revert_patches "--recount" + revert_patches restore_files [[ "$KEEP_TMP" -eq 0 ]] && rm -rf "$TMP_DIR" return 0 @@ -282,7 +282,7 @@ set_module_name() { } # Hardcode the value printed by the localversion script to prevent patch -# application from appending it with '+' due to a dirty git working tree. +# application from appending it with '+' due to a dirty working tree. set_kernelversion() { local file="$SRC/scripts/setlocalversion" local localversion @@ -300,8 +300,8 @@ get_patch_input_files() { local patch="$1" grep0 -E '^--- ' "$patch" \ + | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \ | gawk '{print $2}' \ - | grep0 -v '^/dev/null$' \ | sed 's|^[^/]*/||' \ | sort -u } @@ -310,8 +310,8 @@ get_patch_output_files() { local patch="$1" grep0 -E '^\+\+\+ ' "$patch" \ + | grep0 -v -e '/dev/null' -e '1969-12-31' -e '1970-01-01' \ | gawk '{print $2}' \ - | grep0 -v '^/dev/null$' \ | sed 's|^[^/]*/||' \ | sort -u } @@ -323,21 +323,6 @@ get_patch_files() { | sort -u } -# Make sure git re-stats the changed files -git_refresh() { - local patch="$1" - local files=() - - [[ ! -e "$SRC/.git" ]] && return - - get_patch_input_files "$patch" | mapfile -t files - - ( - cd "$SRC" - git update-index -q --refresh -- "${files[@]}" - ) -} - check_unsupported_patches() { local patch @@ -358,36 +343,19 @@ check_unsupported_patches() { apply_patch() { local patch="$1" - shift - local extra_args=("$@") [[ ! -f "$patch" ]] && die "$patch doesn't exist" - - ( - cd "$SRC" - - # The sed strips the version signature from 'git format-patch', - # otherwise 'git apply --recount' warns. - sed -n '/^-- /q;p' "$patch" | - git apply "${extra_args[@]}" - ) + patch -d "$SRC" -p1 --dry-run --silent --no-backup-if-mismatch -r /dev/null < "$patch" + patch -d "$SRC" -p1 --silent --no-backup-if-mismatch -r /dev/null < "$patch" APPLIED_PATCHES+=("$patch") } revert_patch() { local patch="$1" - shift - local extra_args=("$@") local tmp=() - ( - cd "$SRC" - - sed -n '/^-- /q;p' "$patch" | - git apply --reverse "${extra_args[@]}" - ) - git_refresh "$patch" + patch -d "$SRC" -p1 -R --silent --no-backup-if-mismatch -r /dev/null < "$patch" for p in "${APPLIED_PATCHES[@]}"; do [[ "$p" == "$patch" ]] && continue @@ -406,11 +374,10 @@ apply_patches() { } revert_patches() { - local extra_args=("$@") local patches=("${APPLIED_PATCHES[@]}") for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do - revert_patch "${patches[$i]}" "${extra_args[@]}" + revert_patch "${patches[$i]}" done APPLIED_PATCHES=() @@ -434,6 +401,7 @@ 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)" validate_config set_module_name @@ -459,12 +427,12 @@ refresh_patch() { ( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) # Copy patched source files to 'b' - apply_patch "$patch" --recount + apply_patch "$patch" ( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) - revert_patch "$patch" --recount + revert_patch "$patch" # Diff 'a' and 'b' to make a clean patch - ( cd "$tmpdir" && git diff --no-index --no-prefix a b > "$patch" ) || true + ( cd "$tmpdir" && diff -Nupr a b > "$patch" ) || true } # Copy the patches to a temporary directory, fix their lines so as not to @@ -487,8 +455,7 @@ fix_patches() { cp -f "$old_patch" "$tmp_patch" refresh_patch "$tmp_patch" - "$FIX_PATCH_LINES" "$tmp_patch" > "$new_patch" - refresh_patch "$new_patch" + "$FIX_PATCH_LINES" "$tmp_patch" | recountdiff > "$new_patch" PATCHES[i]="$new_patch" -- cgit v1.2.3 From e4dbf70615e52255de3ff943ac08e0bbd080dcd6 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:44 -0400 Subject: livepatch/klp-build: add grep-override function Provide a custom grep() function to catch direct usage of the command. Bare grep calls are generally incompatible with pipefail and errexit behavior (where a failed match causes the script to exit). Developers can still call grep via command grep if that behavior is explicitly desired. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-6-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 564985a1588a..cf6c2bf694aa 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -56,6 +56,13 @@ grep0() { 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 +} + status() { echo "$*" } -- cgit v1.2.3 From 0573bcc4ffca498a6c644b0e1ccbe1a6d9b96a5c Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:45 -0400 Subject: livepatch/klp-build: add Makefile with check target Add a standalone Makefile with a 'check' target that runs static code analysis (shellcheck) on the klp-build script(s). This is intended strictly as a development aid. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-7-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/Makefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scripts/livepatch/Makefile (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/Makefile b/scripts/livepatch/Makefile new file mode 100644 index 000000000000..17b590213740 --- /dev/null +++ b/scripts/livepatch/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# Standalone Makefile for developer tooling (not part of kbuild). + +SHELLCHECK := $(shell which shellcheck 2> /dev/null) + +SRCS := \ + klp-build + +.DEFAULT_GOAL := help +.PHONY: help +help: + @echo " check - Run shellcheck on $(SRCS)" + @echo " help - Show this help message" + +.PHONY: check +check: +ifndef SHELLCHECK + $(error shellcheck is not installed. Please install it to run checks) +endif + @$(SHELLCHECK) $(SHELLCHECK_OPTIONS) $(SRCS) -- cgit v1.2.3 From b4a53519393521c68ec65f43bfebd64f178e6220 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:46 -0400 Subject: livepatch/klp-build: fix shellcheck complaints Fix or suppress the following shellcheck warnings: In klp-build line 57: command grep "$@" || true ^--^ SC2317 (info): Command appears to be unreachable. Check usage (or ignore if invoked indirectly). Fix the following warning: In klp-build line 565: local file_dir="$(dirname "$file")" ^------^ SC2034 (warning): file_dir appears unused. Verify use (or export if used externally). Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-8-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index cf6c2bf694aa..374e1261fd7a 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -53,6 +53,7 @@ PATCH_TMP_DIR="$TMP_DIR/tmp" KLP_DIFF_LOG="$DIFF_DIR/diff.log" grep0() { + # shellcheck disable=SC2317 command grep "$@" || true } @@ -550,7 +551,6 @@ copy_orig_objects() { for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" local file="$OBJ/$rel_file" - local file_dir="$(dirname "$file")" local orig_file="$ORIG_DIR/$rel_file" local orig_dir="$(dirname "$orig_file")" -- cgit v1.2.3 From e506ad210d6d7aeaff4bca777428c8c8f9850150 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:47 -0400 Subject: livepatch/klp-build: improve short-circuit validation Update SHORT_CIRCUIT behavior to better handle patch validation and argument processing in later klp-build steps. Perform patch validation for both step 1 (building original kernel) and step 2 (building patched kernel) to ensure patches are verified before any compilation occurs. Additionally, allow the user to omit input patches when skipping past step 2. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-9-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 374e1261fd7a..60c7635e65c1 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -220,7 +220,7 @@ process_args() { esac done - if [[ $# -eq 0 ]]; then + if [[ $# -eq 0 ]] && (( SHORT_CIRCUIT <= 2 )); then usage exit 1 fi @@ -791,9 +791,12 @@ build_patch_module() { process_args "$@" do_init -if (( SHORT_CIRCUIT <= 1 )); then +if (( SHORT_CIRCUIT <= 2 )); then status "Validating patch(es)" validate_patches +fi + +if (( SHORT_CIRCUIT <= 1 )); then status "Building original kernel" clean_kernel build_kernel -- cgit v1.2.3 From b41d8b7d1752f2f85fc1a87f5e4f4dda45adad15 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:49 -0400 Subject: livepatch/klp-build: provide friendlier error messages Provide more context for common klp-build failure modes. Clarify which user-provided patch is unsupported or failed to apply, and explicitly identify which kernel build (original or patched) failed. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-11-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 60c7635e65c1..dc0a23a8908b 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -342,7 +342,7 @@ check_unsupported_patches() { for file in "${files[@]}"; do case "$file" in lib/*|*.S) - die "unsupported patch to $file" + die "${patch}: unsupported patch to $file" ;; esac done @@ -487,6 +487,7 @@ clean_kernel() { } build_kernel() { + local build="$1" local log="$TMP_DIR/build.log" local objtool_args=() local cmd=() @@ -524,7 +525,7 @@ build_kernel() { "${cmd[@]}" \ 1> >(tee -a "$log") \ 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) - ) + ) || die "$build kernel build failed" } find_objects() { @@ -799,7 +800,7 @@ fi if (( SHORT_CIRCUIT <= 1 )); then status "Building original kernel" clean_kernel - build_kernel + build_kernel "original" status "Copying original object files" copy_orig_objects fi @@ -809,7 +810,7 @@ if (( SHORT_CIRCUIT <= 2 )); then fix_patches apply_patches status "Building patched kernel" - build_kernel + build_kernel "patched" revert_patches status "Copying patched object files" copy_patched_objects -- cgit v1.2.3 From 1fbc9b855f08f89ccf933324a5cbd8c53ee94d87 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:50 -0400 Subject: livepatch/klp-build: add terminal color output Improve the readability of klp-build output by implementing a basic color scheme. When the standard output and error are connected to a terminal, highlight status messages in bold and warning/error prefixes in yellow/red. Acked-by: Song Liu Signed-off-by: Joe Lawrence Link: https://patch.msgid.link/20260310203751.1479229-12-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index dc0a23a8908b..d628e2c86078 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -52,6 +52,15 @@ PATCH_TMP_DIR="$TMP_DIR/tmp" KLP_DIFF_LOG="$DIFF_DIR/diff.log" +# Terminal output colors +read -r COLOR_RESET COLOR_BOLD COLOR_ERROR COLOR_WARN <<< "" +if [[ -t 1 && -t 2 ]]; then + COLOR_RESET="\033[0m" + COLOR_BOLD="\033[1m" + COLOR_ERROR="\033[0;31m" + COLOR_WARN="\033[0;33m" +fi + grep0() { # shellcheck disable=SC2317 command grep "$@" || true @@ -65,15 +74,15 @@ grep() { } status() { - echo "$*" + echo -e "${COLOR_BOLD}$*${COLOR_RESET}" } warn() { - echo "error: $SCRIPT: $*" >&2 + echo -e "${COLOR_WARN}warning${COLOR_RESET}: $SCRIPT: $*" >&2 } die() { - warn "$@" + echo -e "${COLOR_ERROR}error${COLOR_RESET}: $SCRIPT: $*" >&2 exit 1 } @@ -110,7 +119,7 @@ cleanup() { } trap_err() { - warn "line ${BASH_LINENO[0]}: '$BASH_COMMAND'" + die "line ${BASH_LINENO[0]}: '$BASH_COMMAND'" } trap cleanup EXIT INT TERM HUP -- cgit v1.2.3 From 51a0b7c4ede5c775e9d362e5f465ca993e076823 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 10 Mar 2026 16:37:51 -0400 Subject: livepatch/klp-build: report patch validation fuzz Capture the output of the patch command to detect when a patch applies with fuzz or line offsets. If such "fuzz" is detected during the validation phase, warn the user and display the details. This helps identify input patches that may need refreshing against the target source tree. Ensure that internal patch operations (such as those in refresh_patch or during the final build phase) can still run quietly. Signed-off-by: Joe Lawrence Acked-by: Song Liu Link: https://patch.msgid.link/20260310203751.1479229-13-joe.lawrence@redhat.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'scripts/livepatch') diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index d628e2c86078..839f9b6bfe1f 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -360,11 +360,24 @@ check_unsupported_patches() { apply_patch() { local patch="$1" + shift + local extra_args=("$@") + local drift_regex="with fuzz|offset [0-9]+ line" + local output + local status [[ ! -f "$patch" ]] && die "$patch doesn't exist" - patch -d "$SRC" -p1 --dry-run --silent --no-backup-if-mismatch -r /dev/null < "$patch" - patch -d "$SRC" -p1 --silent --no-backup-if-mismatch -r /dev/null < "$patch" + status=0 + output=$(patch -d "$SRC" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? + if [[ "$status" -ne 0 ]]; then + echo "$output" >&2 + die "$patch did not apply" + elif [[ "$output" =~ $drift_regex ]]; then + echo "$output" >&2 + warn "${patch} applied with fuzz" + fi + patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" APPLIED_PATCHES+=("$patch") } @@ -383,10 +396,11 @@ revert_patch() { } apply_patches() { + local extra_args=("$@") local patch for patch in "${PATCHES[@]}"; do - apply_patch "$patch" + apply_patch "$patch" "${extra_args[@]}" done } @@ -444,7 +458,7 @@ refresh_patch() { ( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) # Copy patched source files to 'b' - apply_patch "$patch" + apply_patch "$patch" "--silent" ( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) revert_patch "$patch" @@ -817,7 +831,7 @@ fi if (( SHORT_CIRCUIT <= 2 )); then status "Fixing patch(es)" fix_patches - apply_patches + apply_patches "--silent" status "Building patched kernel" build_kernel "patched" revert_patches -- cgit v1.2.3