From de50894b83184331d3fa73dff04b1e048a9dd8d5 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Mar 22 2024 14:32:25 +0000 Subject: Merge branch 'users/pmladek/scripts/check-kernel-fix' into users/pmladek/scripts/cve-scripts --- diff --git a/scripts/check-kernel-fix b/scripts/check-kernel-fix index c384ce2..9ce76ec 100755 --- a/scripts/check-kernel-fix +++ b/scripts/check-kernel-fix @@ -5,17 +5,38 @@ usage() { - echo "Check whether a sha or reference is missing in any branch" + echo "Check state of a kernel fix and eventually suggest needed actions" echo - echo "Usage: ${0##*/} [-c] [-t] sha reference..." + echo "The actions are proposed only when \"sha\" of an upstream commit" + echo "is passed as a parameter. Otherwise, it just checks where" + echo "the given references are missing." + echo + echo "When \"sha\" is defined then the script checks whether the commit" + echo "is already in the upstream baseline or backported. When backported," + echo "it checks whether it has all the given references." + echo + echo "Also the script looks for \"Fixes:\" tag of the given \"sha\"." + echo "When defined, the scripts informs where the fix has to be backported." + echo "Otherwise, the script just informs that the decision has to be" + echo "made manually." + echo + echo "The script also takes into account the hierarchy of branches." + echo "It checks all branches. But the action is proposed only for" + echo "the top level ones. The assumption is that the other branches" + echo "will get the fix via a merge." + echo + echo "Usage: ${0##*/} [options] [sha] [reference]..." echo echo "Parameters:" - echo " hash: hash of the upstream commit associated with the patch" + echo " sha: sha of the upstream commit" echo " reference: reference to be checked, e.g. bsc#123456 and/or CVE-XXXX-YYYY" - echo " -a: show actions which has to be done (shows only top level branches)" - echo " -c: check also cve branches" + echo + echo "Options:" + echo " -c: check also CVE branches" echo " -h: help" - echo " -p: show progress when checking branches" + echo " -q: quiet mode (no progress)" + echo " -s: show state of branch where an action would be needed" + echo " -v: verbose mode: show state of each branch and even NOP actions" } branch= @@ -31,113 +52,138 @@ branches_conf="$(fetch_branches)" tmpdir=$(mktemp -d /tmp/${0##*/}.XXXXXX) trap 'rm -rf "$tmpdir"' EXIT -state_file="$tmpdir/sha-state" -merged_state_file="$tmpdir/meged-sha-state" +branch_state_file="$tmpdir/branch-state" +patch_file="$tmpdir/patches" actions_file="$tmpdir/actions" -print_sha_state() +# Check which references are missing in the given branch. +# Used when sha is not known. +# Can be used to check a consistency of references. +check_branch_references() +{ + local branch="$1" + shift + local references="$@" + + [ -z "$branch" ] && fail "check_branch_references: No branch provided" + [ -z "$references" ] && fail "check_branch_references: No references provided" + + local patches=$(references_to_patches_in_branch $branch $references) + if [ -z "$patches" ] ; then + if [ -n "$verbose_mode" ] ; then + echo "$branch:not_found_references:$references" + return + fi + fi + + for patch in $patches ; do + local missing_references= + + for ref in $references ; do + if ! patch_has_reference_in_branch "$patch" "$ref" "$branch" ; then + [ -n "$missing_references" ] && missing_references="$missing_references " + missing_references="$missing_references$ref" + fi + done + + if [ -n "$missing_references" ] ; then + echo "$branch:$patch:missing_references:$missing_references" + else + echo "$branch:$patch:ok" + fi + done +} + +print_branch_state() { local msg="$@" - if [ -n "$show_progress" ] ; then + if [ -n "$verbose_mode" ] ; then + echo "$msg" + elif [ -z "$quiet_mode" ] ; then + # show progress echo -n "." - else - verbose_msg "$msg" fi - echo "$msg" >> "$state_file" + echo "$msg" >> "$branch_state_file" } -check_sha_ref_state() +# Check state of the given branch and store +# The states are stored $tmpdir/branch-state are are the following: +# +# + nope: branch not affected +# + ok: branch has the fix and all references +# + missing_references: all or some references were not found +# + missing_patch: patch has to be backported +# + maybe_missing_patch: patch is missing and it is not known which commit +# introduced the bug +# +# When found, the name of the patch is stored into "$patch_file". +check_branch_state() { local branch="$1" local sha="$2" - local ref="$3" + shift 2 + local references="$@" - [ -z "$branch" ] && fail "check_sha_ref_state: No branch provided" - [ -z "$ref" ] && fail "check_sha_ref_state: No ref provided" - [ -z "$sha" ] && fail "check_sha_ref_state: No sha provided" + [ -z "$branch" ] && fail "check_branch_state: No branch provided" + [ -z "$sha" ] && fail "check_branch_state: No sha provided" - msg_prefix="$branch:$sha" + local patch= + local base= + local ref= + local missing_references= + local msg_prefix="$branch:$sha" - local base=$(branch_base_ver $branch) - patch=$(sha_to_patch_in_branch "$sha" "$branch") + base=$(branch_base_ver $branch) # Already merged upstream? if sha_merged_in_upstream_tag "$sha" "$base" ; then - print_sha_state "$msg_prefix:nope" + print_branch_state "$msg_prefix:nope" return fi - # Does the patch exist and has the reference? + # Does the patch exist? + patch=$(sha_to_patch_in_branch "$sha" "$branch") + if [ -n "$patch" ] ; then - if patch_has_reference_in_branch "$patch" "$ref" "$branch" ; then - print_sha_state "$msg_prefix:ok" + echo "$branch:$patch" >> "$patch_file" + + # Check references + for ref in $references ; do + if ! patch_has_reference_in_branch "$patch" "$ref" "$branch" ; then + [ -n "$missing_references" ] && missing_references="$missing_references " + missing_references="$missing_references$ref" + fi + done + + if [ -z "$missing_references" ] ; then + print_branch_state "$msg_prefix:ok" else - print_sha_state "$msg_prefix:missing_ref:$ref" + print_branch_state "$msg_prefix:missing_references:$missing_references" fi return fi + # Sha is not backported # Do we need to backport it because of the Fixes tag? - if sha_has_git_fixes "$sha" "$base" ; then - local affected_by_git_fixes="$(sha_affected_by_git_fixes "$sha" "$base" "$branch")" + local sha_git_fixes=$(sha_get_upstream_git_fixes $sha) + if [ -n "$sha_git_fixes" ] ; then + local affected_by_git_fixes="$(affected_by_git_fixes "$branch" "$base" $sha_git_fixes)" if [ -n "$affected_by_git_fixes" ] ; then - print_sha_state "$msg_prefix:missing_patch:$affected_by_git_fixes" + print_branch_state "$msg_prefix:missing_patch:$affected_by_git_fixes" else - print_sha_state "$msg_prefix:nope" + print_branch_state "$msg_prefix:nope" fi return fi # missing git fixes - print_sha_state "$msg_prefix:might_be_missing:$ref" -} - -merge_one_state() -{ - local branch="$1" - local sha="$2" - local state="$3" - - local references= - local ref= - - [ -z "$branch" ] && fail "merge_one_state: No branch provided" - [ -z "$sha" ] && fail "merge_one_state: No sha provided" - [ -z "$state" ] && fail "merge_one_state: No state provided" - - if [ "$state" == "missing_ref" -o "$state" == "might_be_missing" -o "$state" == "missing_patch" ] ; then - for ref in $(grep "^$branch:$sha:$state:" "$state_file" | cut -d: -f4 | sort -u) ; do - if [ -z "references" ] ; then - references="$ref" - else - references="$references $ref" - fi - done - echo "$branch:$sha:$state:$references" >> "$merged_state_file" - else - echo "$branch:$sha:$state" >> "$merged_state_file" - fi -} - -merge_states() -{ - local branch= - local sha= - local state= - - for branch in $(cat "$state_file" | cut -d: -f1 | sort -u) ; do - for sha in $(grep "^$branch:" "$state_file" | cut -d: -f2 | sort -u) ; do - for state in $(grep "^$branch:$sha:" "$state_file" | cut -d: -f3 | sort -u) ; do - merge_one_state "$branch" "$sha" "$state" - done - done - done + print_branch_state "$msg_prefix:maybe_missing_patch:$ref" } print_action() @@ -145,36 +191,51 @@ print_action() local branch="$1" local sha="$2" local state="$3" - local refereces="$@" + shift 3 + local references="$@" [ -z "$branch" ] && fail "print_action: No branch provided" [ -z "$sha" ] && fail "print action: No sha provided" [ -z "$state" ] && fail "print action: No state provided" - [ -z "$references" ] && fail "print action: No references provided" + # make sure tha the file exists + touch "$patch_file" + + patch= action= case "$state" in missing_patch) - action="$branch: MANUAL: backport $sha $references" + action="$branch: MANUAL: backport $sha ($references)" ;; - might_be_missing) - action="$branch: MANUAL: might be missing $sha $references" + + maybe_missing_patch) + action="$branch: MANUAL: might need backport of $sha ($references)" ;; - missing_ref) - action="$branch: RUN: add-missing-reference $sha $references" + + missing_references) + patch=$(grep "^$branch:" "$patch_file" | cut -d : -f 2) + if [ -n "$patch" ] ; then + action="$branch: RUN: add-missing-reference $patch $references" + else + action="$branch: MANUAL: no patch has the references: $references" + fi ;; + nope) - [ -n "$verbose_mode" ] && action="%branch: NOPE: no problema for $sha $references" + [ -n "$verbose_mode" ] && action="$branch: NOPE: no problema for $sha $references" ;; + ok) - [ -n "$verbose_mode" ] && action="%branch: NOPE: up-to-date $sha $references" + [ -n "$verbose_mode" ] && action="$branch: NOPE: up-to-date $sha $references" ;; + *) - fail "print_action: Unknown action: $action" + echo "print_action: Unknown action: $action" >&2 + echo "for $branch:$sha:$state:$references" >&2 + exit 1 ;; esac - if [ -n "$action" ] ; then if [ ! -e "$actions_file" ] ; then # first action @@ -182,13 +243,7 @@ print_action() touch "$actions_file" fi - push_list_msg "action" "$action" - - # FIXME: Could the action ever be duplicated? - if ! grep -q "^$action$" "$actions_file" ; then - echo "$action" - echo "$action" >> "actions_file" - fi + echo "$action" fi } @@ -200,10 +255,12 @@ find_and_print_toplevel_actions() local mb_line= local line= local merge_found= + local state= + local mb_state= [ -z "$branch" ] && fail "find_and_print_toplevel_actions: No branch provided" - grep "^$branch:" "$merged_state_file" | \ + grep "^$branch:" "$branch_state_file" | \ while read line ; do for merge_branch in $(print_merge_branches $branches_conf $branch) ; do @@ -211,10 +268,24 @@ find_and_print_toplevel_actions() mb_line=$(echo -n "$line" | sed -e "s|^$branch:|$merge_branch:|") # ignore the state when the same change is needed in a merge branch - if grep -q "^$mb_line$" "$merged_state_file" ; then + if grep -q "^$mb_line$" "$branch_state_file" ; then merge_found=1 fi + state=$(echo $line | cut -d: -f3) + mb_state=$(echo $mb_line | cut -d: -f3) + + if [ "$state" == "missing_references" -o \ + "$state" == "missing_patch" -o \ + "$state" == "maybe_missing_patch" ] ; then + + # No action is needed when the patch is backported + # and has all the references in the merge branch + if [ "$mb_state" == "ok" ] ; then + merge_found=1 + fi + fi + done if [ -z "$merge_found" ] ; then @@ -224,31 +295,12 @@ find_and_print_toplevel_actions() done } -show_summary() -{ - local problems= - - if grep -q -e "missing_ref" "$state_file" ; then - problems="missing referece" - fi - if grep -q -e "missing_patch" "$state_file" ; then - [ -n "$problems" ] && problems="$problems and " - problems="${problems}missing patch" - fi - - if [ -z "$problems" ] ; then - echo "Summary: OK" - else - echo "Summary: ACTION_NEEDED ($problems)" - cat "$merged_state_file" - fi -} - check_cve= verbose_mode= -show_progress= +quiet_mode= +show_only_states= -while getopts "ahcvp" OPT +while getopts "shcvq" OPT do case $OPT in h) @@ -258,50 +310,75 @@ do c) check_cve="-c" ;; - a) - show_actions=1 + s) + show_only_states=1 ;; v) verbose_mode=1 ;; - p) - show_progress=1 + q) + quiet_mode=1 ;; esac done shift "$(($OPTIND-1))" -[ -n "$verbose_mode" ] && show_progress= +[ -n "$verbose_mode" ] && quiet_mode= -sha="$1" -if [ -z "$sha" ] ; then - echo "No commit provided" +if [ -z "$1" ] ; then + echo "No references provided" usage exit 1 fi -shift -references="$@" -if [ -z "$references" ] ; then - echo "Error: missing reference" - usage - exit 1 -fi +sha= +references= -for ref in $references ; do - for_each_build_branch $check_cve "$branches_conf" check_sha_ref_state "$sha" "$ref" +while [ -n "$1" ] ; do + case "$1" in + CVE*) + references="$references $1" + ;; + *#*) + references="$references $1" + ;; + *) + if ! sha_in_upstream "$1" ; then + fail "Can find't sha in upstream: $1" + fi + + if [ -n "$sha" ] ; then + fail "Only one sha is supported: $sha vs. $1" + fi + + sha="$1" + ;; + esac + shift done -# newline after the dots -[ -n "$show_progress" ] && echo -# The state of each branch is checked separately for each referece. -# Merge same states for each branch. -merge_states +if [ -n "$sha" ] ; then + # Check state of each branch + for_each_build_branch $check_cve "$branches_conf" check_branch_state $sha $references + + # Newline after the dots showing progress + [ -z "$quiet_mode" ] && echo + + if [ -z "$show_only_states" ] ; then + for_each_build_branch $check_cve "$branches_conf" find_and_print_toplevel_actions + + if [ ! -e "$actions_file" ] ; then + echo "EVERYTHING IS OK!" + fi + fi -if [ -n "$show_actions" ] ; then - for_each_build_branch $check_cve "$branches_conf" find_and_print_toplevel_actions else - show_summary + + # No actions are suggested without sha + # Just check which branches have the references + # and if the references are consistent + for_each_build_branch $check_cve "$branches_conf" check_branch_references $references + fi diff --git a/scripts/common-functions b/scripts/common-functions index 05fed66..d606d68 100644 --- a/scripts/common-functions +++ b/scripts/common-functions @@ -72,7 +72,6 @@ print_merge_branches() [ -z "$branches_conf" ] && fail "megre_branches: No branches_conf provided" [ -z "$branch" ] && fail "merge_branches: No branch provided" -# echo " looking for merge branch for: $branch" for word in $(grep -w "^$branch:" "$branches_conf") ; do if [ "${word#merge:}" != "$word" ] ; then merge_branch="${word#merge:}" @@ -120,11 +119,6 @@ for_each_build_branch() done } -verbose_msg() -{ - [ -n "$verbose_mode" ] && echo "$@" -} - fail() { echo $* >&2 @@ -133,23 +127,23 @@ fail() branch_base_ver() { - local branch="origin/$1" - git show-ref --verify --quiet "refs/remotes/${branch}" || fail "$branch invalid branch" + local branch="origin/$1" + git show-ref --verify --quiet "refs/remotes/${branch}" || fail "$branch invalid branch" - local base_ver="v$(git grep SRCVERSION $branch -- rpm/config.sh | sed 's@.*=@@')" + local base_ver="v$(git grep SRCVERSION $branch -- rpm/config.sh | sed 's@.*=@@')" - echo $base_ver + echo $base_ver } sha_get_upstream_git_fixes() { - local sha=$1 - local upstream_git=${2:-$LINUX_GIT} - - [ -z "$sha" ] && fail "No commit provided" - [ -z "$upstream_git" ] && fail "No upstream git tree" + local sha=$1 + local upstream_git=${2:-$LINUX_GIT} + + [ -z "$sha" ] && fail "No commit provided" + [ -z "$upstream_git" ] && fail "No upstream git tree" - git --git-dir="$upstream_git/.git" show $sha | grep -i "^[[:space:]]*fixes:" | awk '{print $2}' + git --git-dir="$upstream_git/.git" show $sha | grep -i "^[[:space:]]*fixes:" | awk '{print $2}' } sha_merged_in_upstream_tag() @@ -165,6 +159,18 @@ sha_merged_in_upstream_tag() git --git-dir="$LINUX_GIT/.git" merge-base --is-ancestor "$sha" "$base" } +sha_in_upstream() +{ + local sha=$1 + local upstream_git=${3:-$LINUX_GIT} + + [ -z "$sha" ] && fail "sha_in_upstream: No sha provided" + [ -z "$upstream_git" ] && fail "sha_in_upstream: No upstream git tree" + + sha_merged_in_upstream_tag $sha origin/HEAD $upstream_git 2>/dev/null +} + + sha_has_git_fixes() { local sha="$1" @@ -182,24 +188,24 @@ sha_has_git_fixes() } -sha_affected_by_git_fixes() +affected_by_git_fixes() { - local sha="$1" + local branch="$1" local base="$2" - local branch="$3" - local upstream_git=${4:-$LINUX_GIT} + shift 2 + local git_fixes="$@" - [ -z "$sha" ] && fail "sha_affected_by_git_fixes: No sha provided" - [ -z "$base" ] && fail "sha_affected_by_git_fixes: No tag provided" - [ -z "$branch" ] && fail "sha_affected_by_git_fixes: No branch provided" - [ -z "$upstream_git" ] && fail "sha_affected_by_git_fixes: No upstream_git provided" + [ -z "$branch" ] && fail "affected_by_git_fixes: No branch provided" + [ -z "$base" ] && fail "affected_by_git_fixes: No tag provided" + [ -z "$git_fixes" ] && fail "affected_by_git_fixes: No git fixes provided" # Check git fixes when the bug was introduced - local needs_fix= local git_fix= local affected_by= - for git_fix in $(sha_get_upstream_git_fixes $sha) ; do + for git_fix in $git_fixes ; do + local needs_fix= + # Is it merged in the upstream base kernel? if sha_merged_in_upstream_tag "$git_fix" "$base" ; then needs_fix=1 @@ -224,7 +230,6 @@ sha_affected_by_git_fixes() fi } - sha_to_patch_in_branch() { local sha="$1" @@ -235,11 +240,7 @@ sha_to_patch_in_branch() branch_file=$(git --no-pager grep -l -i "^git-commit[[:space:]]*:[[:space:]]*$sha" "origin/$branch") - if [ -n "$branch_file" ] ; then - echo "${branch_file#origin/$branch:}" - else - false - fi + echo "${branch_file#origin/$branch:}" } sha_to_patch() @@ -256,12 +257,37 @@ sha_merged_in_suse_tree() local sha="$1" local branch="$2" - [ -z "$sha" ] && fail "sha_to_patch_in_branch: No sha provided" - [ -z "$branch" ] && fail "sha_to_patch_in_branch: No branch provided" + [ -z "$sha" ] && fail "sha_merged_in_suse_tree: No sha provided" + [ -z "$branch" ] && fail "sha_merged_in_suse_tree: No branch provided" - sha_to_patch_in_branch "$sha" "$branch" >/dev/null + local patch=$(sha_to_patch_in_branch "$sha" "$branch") + + test -n "$patch" } +references_to_patches_in_branch() +{ + local branch="$1" + shift + local references="$@" + + [ -z "$branch" ] && fail "references_to_patches_in_branch: No branch provided" + [ -z "$references" ] && fail "references_to_patches_in_branch: No references provided" + + local pattern_prefix="^references[[:space:]]*:[[:space:]]*" + local pattern= + + for ref in $references ; do + [ -n "$pattern" ] && pattern="$pattern|" + pattern="$pattern$pattern_prefix$ref" + done + + branch_files=$(git --no-pager grep -l -E -i "$pattern" "origin/$branch") + + for branch_file in $branch_files ; do + echo "${branch_file#origin/$branch:}" + done +} patch_has_reference() {