#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright 2018-2020 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
# Copyright 2024-2025 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#
# Given a git commit id, find the release(s) it was found in and the git ids
# they are in those releases.
#
# The output of this is:
#   VERSION:GIT_ID
# and the output is sorted in version order (according to 'sort -V')
#
# This script is to be used with 'dyad' in trying to find the proper kernel
# releases where a commit ends up in.  Because of that, reverts MUST be taken
# into account.
#
# If a commit is in a release, and reverted in that release, then it will NOT
# show up in this list as officially this branch did not ever contain the
# release.  Also, if a revert for a commit happens in in an older release, then
# the release is not listed as only the most recent state should be accounted
# for.  If a commit is in a branch, and then later is reverted, it is not
# recorded at ALL for that branch, as essencially it is not present there at
# all.
#
# For example, look at commit c45beebfde34 ("ovl: support encoding fid from
# inode with no alias")
# This commit was released in 6.13, with that commit id, so that will show up
# in the output.
#
# It was also backported to the 6.6.y and 6.12.y branches:
#   For 6.12.y it showed up as commit id 3c7c90274ae339e1ad443c9be1c67a20b80b9c76
#
#   For 6.6.y it is more complex.  It was originally applied to release 6.6.72
#   as commit id a1a541fbfa7e97c1100144db34b57553d7164ce5.  But that backport
#   turned out to have problems as other changes around it were not properly
#   backported, so it was reverted in commit 6.6.73 as commit id
#   950b604384fd75d62e860bec7135b2b62eb4d508.  And then, in 6.6.74 it was
#   brought back as commit id f0c0ac84de17c37e6e84da65fb920f91dada55ad.
#   Because of this, we ONLY want to count the 6.6.y branch as the last commit
#   that it showed up, which is 6.6.74.
#
# So the output of this script, for commit c45beebfde34 will be:
#	6.6.74:f0c0ac84de17c37e6e84da65fb920f91dada55ad
#	6.12.10:3c7c90274ae339e1ad443c9be1c67a20b80b9c76
#	6.13:c45beebfde34aa71afbc48b2c54cdda623515037
#
#

# Initialize our color variables if we are a normal terminal
if [[ -t 1 ]]; then
	txtred=$(tput setaf 1)  # Red
	txtgrn=$(tput setaf 2)  # Green
	txtblu=$(tput setaf 4)  # Blue
	txtcyn=$(tput setaf 6)  # Cyan
	txtrst=$(tput sgr0)     # Text reset
else
	txtred=""
	txtgrn=""
	txtblu=""
	txtcyn=""
	txtrst=""
fi

DEBUG=0
dbg()
{
	if [[ ${DEBUG} -ge 1 ]] ; then
		echo "${txtcyn}# ${1}${txtrst}"
	fi
}

REAL_SCRIPT=$(realpath -e "${BASH_SOURCE[0]}")
SCRIPT_TOP="${SCRIPT_TOP:-$(dirname "${REAL_SCRIPT}")}"

#source "${SCRIPT_TOP}"/lib/common

# Location of our database.  Should be initialized already.
DB="${SCRIPT_TOP}"/verhaal.db

# Location of the stable kernel tree, to cannonize the git id and verify it is
# a valid one to start with
GIT_TREE=${CVEKERNELTREE}
if [[ "${GIT_TREE}" == "" ]]; then
	echo "${txtred}Error: must provide ${txtcyn}CVEKERNELTREE${txtrst} environment variable to point to a kernel tree."
	exit 1
fi

if [[ "${1}" == "-v" ]]; then
	DEBUG=1
	shift
fi

test_id=$1
if [ "${test_id}" == "" ] ; then
        echo "$0 ID_TO_FIND"
        exit 1
fi

id=$(cd ${GIT_TREE} && git log -1 --format="%H" ${test_id} 2> /dev/null)
if [[ "${id}" == "" ]]; then
	echo "${txtred}Error:${txtrst} ${txtcyn}${test_id}${txtrst} is not found in the kernel tree."
	exit 1
fi

# Array of releases we find
output=()

# Find all stable branches first
stable_commits=$(sqlite3 ${DB} "SELECT id FROM commits WHERE mainline_id='${id}';")
for sc in ${stable_commits}; do
	# Get the release for this commit
	version=$(sqlite3 ${DB} "SELECT release FROM commits WHERE id='${sc}';")
	dbg "stable_commit='${sc}' is in version ${version}"

	# **POLICY**
	# If the revert is in the same version, we do not add it to the list as
	# that was never a "vulnerable" kernel release.
	revert=$(sqlite3 ${DB} "SELECT id from commits WHERE reverts='${sc}';")
	if [[ "${revert}" != "" ]]; then
		revert_version=$(sqlite3 ${DB} "SELECT release FROM commits WHERE id='${revert}';")
		if [[ "${revert_version}" == "${version}" ]]; then
			dbg "	commit ${sc}:${version} is reverted by ${revert}:${revert_version} same version so skipping"
			continue
		fi
		dbg "	commit ${sc}:${version} is reverted by ${revert}:${revert_version} but different versions, so keeping"
	fi

	# ***POLICY***
	# See if this commit itself is a revert of something else (if so, let's not add it)
	# Note, this can trip you up if you wonder why a commit/branch does not
	# show up as vulnerable/fixed, this takes this out of the list, so
	# watch out.
	revert=$(sqlite3 ${DB} "SELECT reverts FROM commits WHERE id='${sc}';")
	if [[ "${revert}" != "" ]]; then
		# See if what we are reverting is a stable, or a mainline
		# commit.  If stable, skip it, if mainline, it's ok to add (as
		# this is just a backport of an upstream revert).
		mainline=$(sqlite3 ${DB} "SELECT mainline FROM commits WHERE id='${revert}';")
		if [[ "${mainline}" == "0" ]]; then
			dbg "	commit ${sc} reverts the stable commit ${revert} so skip"
			continue
		fi
	fi

	# Save off this version:stable_commit to our list
	output+=("${version}:${sc}")
done

# Check mainline
version=$(sqlite3 ${DB} "SELECT release from commits where id='${id}';")
if [[ "${version}" != "" ]]; then
	dbg "commit ${id} is in mainline release ${version}"
	output+=("${version}:${id}")
fi

# print them out, in sorted order
echo "$(for release in "${output[@]}"; do echo "${release}"; done | sort -V)"

