Blob Blame History Raw
#!/bin/bash
# vim: sw=4:sts=4:et

. $(dirname "$0")/common-functions

usage()
{
    echo "Check state of a kernel fix and eventually suggest needed actions"
    echo
    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 "	sha: sha of the upstream commit"
    echo "	reference: reference to be checked, e.g. bsc#123456 and/or CVE-XXXX-YYYY"
    echo
    echo "Options:"
    echo "	-c: check also CVE branches"
    echo "	-h: help"
    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=
bprefix=
sha=
references=

cve=
top_level=

branches_conf="$(fetch_branches)"

tmpdir=$(mktemp -d /tmp/${0##*/}.XXXXXX)
trap 'rm -rf "$tmpdir"' EXIT

branch_state_file="$tmpdir/branch-state"
patch_file="$tmpdir/patches"
actions_file="$tmpdir/actions"

# 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 "$verbose_mode" ] ; then
	echo "$msg"
    elif [ -z "$quiet_mode" ] ; then
	# show progress
	echo -n "."
    fi

    echo "$msg" >> "$branch_state_file"
}

# 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"
    shift 2
    local references="$@"

    [ -z "$branch" ] && fail "check_branch_state: No branch provided"
    [ -z "$sha" ] && fail "check_branch_state: No sha provided"

    local patch=
    local base=
    local ref=
    local missing_references=
    local msg_prefix="$branch:$sha"


    base=$(branch_base_ver $branch)

    # Already merged upstream?
    if sha_merged_in_upstream_tag "$sha" "$base" ; then
	print_branch_state "$msg_prefix:nope"
	return
    fi

    # Does the patch exist?
    patch=$(sha_to_patch_in_branch "$sha" "$branch")

    if [ -n "$patch" ] ; then
	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_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?
    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_branch_state "$msg_prefix:missing_patch:$affected_by_git_fixes"
	else
	    print_branch_state "$msg_prefix:nope"
	fi

	return
    fi

    # missing git fixes
    print_branch_state "$msg_prefix:maybe_missing_patch:$ref"
}

print_action()
{
    local branch="$1"
    local sha="$2"
    local state="$3"
    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"

    # make sure tha the file exists
    touch "$patch_file"

    patch=
    action=
    case "$state" in
	missing_patch)
	    action="$branch: MANUAL: backport $sha ($references)"
	    ;;

	maybe_missing_patch)
	    action="$branch: MANUAL: might need backport of $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"
	    ;;

	ok)
	    [ -n "$verbose_mode" ] && action="$branch: NOPE: up-to-date $sha $references"
	    ;;

	*)
	    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
	    echo "ACTION NEEDED!"
	    touch "$actions_file"
	fi

	echo "$action"
    fi
}

find_and_print_toplevel_actions()
{
    local branch="$1"
    local action_parameters=
    local merge_branch=
    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:" "$branch_state_file" | \
	while read line ; do
	    for merge_branch in $(print_merge_branches $branches_conf $branch) ; do

		# branch name might include '/', e.g. cve/linux-4.12
		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$" "$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
		# split line into parameters
		print_action ${line//:/ }
	    fi
	done
}

check_cve=
verbose_mode=
quiet_mode=
show_only_states=

while getopts "shcvq" OPT
do
    case $OPT in
	h)
	    usage
	    exit
	    ;;
	c)
	    check_cve="-c"
	    ;;
	s)
	    show_only_states=1
	    ;;
	v)
	    verbose_mode=1
	    ;;
	q)
	    quiet_mode=1
	    ;;
    esac
done

shift "$(($OPTIND-1))"

[ -n "$verbose_mode" ] && quiet_mode=

if [ -z "$1" ] ; then
    echo "No references provided"
    usage
    exit 1
fi

sha=
references=

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


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

else

    # 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