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 "Expect upstream kernel tree sha as the parameter. The script checks"
    echo "whether the commit is already in the upstream baseline or backported"
    echo "in kernel-source tree. When backported, it checks whether it has CVE"
    echo "reference if there is any"
    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
    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

if ! sha_in_upstream "$1" ; then
	fail "Can find't sha in upstream: $1"
fi

sha=$1
cve=$(sha2cve $sha)
bsc=
[ -n "$cve" ] && bsc=$(cve2bugzilla $cve)
references="$cve $bsc"

# 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