Blob Blame History Raw
From 1d7e8a74bb6c282ebe6c9193e4bd0218ec689e46 Mon Sep 17 00:00:00 2001
From: Josh Poimboeuf <jpoimboe@redhat.com>
Date: Wed, 17 Aug 2022 12:21:04 -0700
Subject: [PATCH 106/108] kpatch-build: support Linux 5.19 .cmd file format

Rewrite kobj_find() to deal with Linux 5.19, where the .cmd files use
object file paths relative to the .cmd file rather than relative to the
root of the kernel tree.

While at it, add several performance enhancements to prevent all
currently known deep finds.

This is all quite fiddly.  But it works.

Fixes #1277.

Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
---
 kpatch-build/kpatch-build | 243 +++++++++++++++++++++++++++++---------
 1 file changed, 188 insertions(+), 55 deletions(-)

diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build
index 5435a19..bec8010 100755
--- a/kpatch-build/kpatch-build
+++ b/kpatch-build/kpatch-build
@@ -434,82 +434,215 @@ find_special_section_data() {
 	return
 }
 
-filter_parent_obj()
-{
-  local dir="${1}"
-  local file="${2}"
+# path of file, relative to dir
+# adapted from https://stackoverflow.com/a/24848739
+relpath() {
+	local file="$1"
+	local dir="$2"
+
+	local filedir
+	local common
+	local result
+
+	filedir="$(dirname "$(readlink -f "$file")")"
+	common="$(readlink -f "$dir")"
+
+	if [[ "$filedir" = "$common" ]]; then
+		basename "$file"
+		return
+	fi
+
+	while [[ "${filedir#$common/}" = "$filedir" ]]; do
+		common="$(dirname "$common")"
+		result="../$result"
+	done
 
-  grep -v "\.mod\.cmd$" | grep -Fv "${dir}/.${file}.cmd"
+	result="${result}${filedir#$common/}"
+	echo "${result}/$(basename "$file")"
 }
 
-find_parent_obj() {
-	dir="$(dirname "$1")"
-	absdir="$(readlink -f "$dir")"
-	pwddir="$(readlink -f .)"
-	pdir="${absdir#$pwddir/}"
-	file="$(basename "$1")"
-	grepname="${1%.o}"
-	grepname="$grepname\\.o"
-	if [[ "$DEEP_FIND" -eq 1 ]]; then
-		num=0
-		if [[ -n "$last_deep_find" ]]; then
-			parent="$(grep -lw "$grepname" "$last_deep_find"/.*.cmd | filter_parent_obj "${pdir}" "${file}" | head -n1)"
-			num="$(grep -lw "$grepname" "$last_deep_find"/.*.cmd | filter_parent_obj "${pdir}" "${file}" | wc -l)"
+cmd_file_to_o_file() {
+	local parent="$1"
+
+	# convert cmd file name to corresponding .o
+	parent_dir="$(dirname "$parent")"
+	parent_dir="${parent_dir#./}"
+	parent="$(basename "$parent")"
+	parent="${parent#.}"
+	parent="${parent%.cmd}"
+	parent="$parent_dir/$parent"
+
+	[[ -f $parent ]] || die "can't find $parent associated with $1"
+
+	echo "$parent"
+}
+
+get_parent_from_parents() {
+	local parents=("$@")
+
+	[[ ${#parents[@]} -eq 0 ]] && PARENT="" && return
+	[[ ${#parents[@]} -eq 1 ]] && PARENT="${parents[0]}" && return
+
+	# multiple parents:
+	local parent
+	local mod_name="${parents[0]%.*}"
+	local mod_file
+	for parent in "${parents[@]}"; do
+		# for modules, there can be multiple matches.  Some
+		# combination of foo.o, foo.mod, and foo.ko, depending
+		# on kernel version and whether the module is single or
+		# multi-object.  Make sure a .mod and/or .ko exists, and no
+		# more than one .mod/.ko exists.
+
+		[[ $parent = *.o ]] && continue
+
+		if [[ ${parent%.*} != "$mod_name" ]]; then
+			mod_file=""
+			break
 		fi
-		if [[ "$num" -eq 0 ]]; then
-			parent="$(find . -name ".*.cmd" -print0 | xargs -0 grep -lw "$grepname" | filter_parent_obj "${pdir}" "${file}" | cut -c3- | head -n1)"
-			num="$(find . -name ".*.cmd" -print0 | xargs -0 grep -lw "$grepname" | filter_parent_obj "${pdir}" "${file}" | wc -l)"
-			[[ "$num" -eq 1 ]] && last_deep_find="$(dirname "$parent")"
+
+		if [[ $parent = *.mod || $parent = *.ko ]]; then
+			mod_file=$parent
+			continue
 		fi
+
+		mod_file=""
+		break
+	done
+
+	if [[ -n $mod_file ]]; then
+		PARENT="$mod_file"
+		return
+	fi
+
+	ERROR_IF_DIFF="multiple parent matches for $file: ${parents[*]}"
+	PARENT="${parents[0]}"
+}
+
+__find_parent_obj_in_dir() {
+	local file="$1"
+	local dir="$2"
+
+	declare -a parents
+
+	while IFS='' read -r parent; do
+		parent="$(cmd_file_to_o_file "$parent")"
+		[[ $parent -ef $file ]] && continue
+		parents+=("$parent")
+	done < <(grep -El "[ 	]${file/./\\.}([ 	\)]|$)" "$dir"/.*.cmd)
+
+	get_parent_from_parents "${parents[@]}"
+}
+
+find_parent_obj_in_dir() {
+	local file="$1"
+	local dir="$2"
+
+	# make sure the dir has .cmd files
+	if ! compgen -G "$dir"/.*.cmd > /dev/null; then
+		PARENT=""
+		return
+	fi
+
+	# 5.19+: ../acp/acp_hw.o
+	__find_parent_obj_in_dir "$(relpath "$file" "$dir")" "$dir"
+	[[ -n $PARENT ]] && return
+
+	# pre-5.19 (and 5.19+ single-object modules):
+	if [[ $file == $dir* ]]; then
+		# arch/x86/kernel/smp.o
+		__find_parent_obj_in_dir "$file" "$dir"
 	else
-		parent="$(grep -lw "$grepname" "$dir"/.*.cmd | filter_parent_obj "${dir}" "${file}" | head -n1)"
-		num="$(grep -lw "$grepname" "$dir"/.*.cmd | filter_parent_obj "${dir}" "${file}" | wc -l)"
+		# drivers/gpu/drm/amd/amdgpu/../acp/acp_hw.o
+		__find_parent_obj_in_dir "$dir"/"$(relpath "$file" "$dir")" "$dir"
 	fi
+}
 
-	[[ "$num" -eq 0 ]] && PARENT="" && return
-	[[ "$num" -gt 1 ]] && ERROR_IF_DIFF="two parent matches for $1"
+find_parent_obj() {
+	local file="$1"
+
+	# common case: look in same directory
+	find_parent_obj_in_dir "$file" "$(dirname "$file")"
+	[[ -n $PARENT ]] && return
 
-	dir="$(dirname "$parent")"
-	PARENT="$(basename "$parent")"
-	PARENT="${PARENT#.}"
-	PARENT="${PARENT%.cmd}"
-	[[ $dir != "." ]] && PARENT="$dir/$PARENT"
-	[[ ! -e "$PARENT" ]] && die "ERROR: can't find parent $PARENT for $1"
+	# if we previously had a successful deep find, try that dir first
+	if [[ -n "$LAST_DEEP_FIND_DIR" ]]; then
+		find_parent_obj_in_dir "$file" "$LAST_DEEP_FIND_DIR"
+		[[ -n "$PARENT" ]] && return
+	fi
+
+	# prevent known deep finds
+	if [[ $file = drivers/gpu/drm/amd/* ]]; then
+		find_parent_obj_in_dir "$file" "drivers/gpu/drm/amd/amdgpu"
+		[[ -n "$PARENT" ]] && return
+	fi
+	if [[ $file = virt/kvm/* ]]; then
+		find_parent_obj_in_dir "$file" "arch/x86/kvm"
+		[[ -n "$PARENT" ]] && return
+	fi
+	if [[ $file = drivers/oprofile/* ]]; then
+		find_parent_obj_in_dir "$file" "arch/x86/oprofile"
+		[[ -n "$PARENT" ]] && return
+	fi
+
+	# check higher-level dirs
+	local dir
+	dir="$(dirname "$file")"
+	while [[ ! $dir -ef . ]]; do
+		dir="$(dirname "$dir")"
+		find_parent_obj_in_dir "$file" "$dir"
+		[[ -n $PARENT ]] && return
+	done
+
+	# slow path: search the entire tree ("deep find")
+	echo 'doing "deep find" for parent object'
+	declare -a parents
+	while IFS= read -r -d '' dir; do
+		find_parent_obj_in_dir "$file" "$dir"
+		if [[ -n $PARENT ]]; then
+			parents+=("$PARENT")
+			LAST_DEEP_FIND_DIR="$dir"
+		fi
+	done < <(find . -type d -print0)
+
+	get_parent_from_parents "${parents[@]}"
 }
 
+# find vmlinux or .ko associated with a .o file
 find_kobj() {
-	arg="$1"
+	local file="$1"
 
 	if [[ -n $OOT_MODULE ]]; then
 		KOBJFILE="$OOT_MODULE"
 		return
 	fi
 
-	KOBJFILE="$arg"
-	DEEP_FIND=0
+	KOBJFILE="$file"
 	ERROR_IF_DIFF=
 	while true; do
+		case "$KOBJFILE" in
+			*.mod)
+				KOBJFILE=${PARENT/.mod/.ko}
+				[[ -e $KOBJFILE ]] || die "can't find .ko for $PARENT"
+				return
+				;;
+			*.ko)
+				return
+				;;
+			*/built-in.o|\
+			*/built-in.a|\
+			arch/x86/kernel/ebda.o|\
+			arch/x86/kernel/head*.o|\
+			arch/x86/kernel/platform-quirks.o|\
+			arch/x86/lib/lib.a|\
+			lib/lib.a)
+				KOBJFILE=vmlinux
+				return
+				;;
+		esac
+
 		find_parent_obj "$KOBJFILE"
-		[[ -n "$PARENT" ]] && DEEP_FIND=0
-		if [[ -z "$PARENT" ]]; then
-			[[ "$KOBJFILE" = *.ko ]] && return
-			case "$KOBJFILE" in
-				*/built-in.o|\
-				*/built-in.a|\
-				arch/x86/lib/lib.a|\
-				arch/x86/kernel/head*.o|\
-				arch/x86/kernel/ebda.o|\
-				arch/x86/kernel/platform-quirks.o|\
-				lib/lib.a)
-					KOBJFILE=vmlinux
-					return
-			esac
-			if [[ "$DEEP_FIND" -eq 0 ]]; then
-				DEEP_FIND=1
-				continue;
-			fi
-			die "invalid ancestor $KOBJFILE for $arg"
-		fi
+		[[ -z "$PARENT" ]] && die "invalid ancestor $KOBJFILE for $file"
 		KOBJFILE="$PARENT"
 	done
 }
-- 
2.37.3