#!/bin/bash
#
# Note: lines starting with "#h#" are printed by the script if called with the parameter "-h" or "--help"
#
#h# 
#h# print_security_patch - print either the security patch of a repository tree for AOSP or an Android ROM zip file
#
# History
#  29.03.2024 v1.0.0.0 /bs
#    initial release
#  08.04.2024 v1.1.0.0 /bs
#    added code to print the security patch level used to create ROM image zipfiles
#    the script now supports multiple parameter
#  14.11.2024 v1.2.0.0 /bs
#    the script now runs with "set -x" if the envionment variable DEBUG is not empty
#    added support for Android 15:
#     The config file used to define the patchlevel and the release changed again in Android 15:
#     The script now reads the config from the file ./build/core/build_id.mk if it exists
#
#  16.01.2025 v1.3.0.0 /bs
#     the script now supports parameter to set variables; the format for the parameter is: "var=value"
#     added code to print the build command for creating the ROM image file (use the parameter PRINT=0 to print the command)
#     added code to print the global variables found in the repository tree (use the parameter PRINTVARS=0 to print the variables and values)
#     added support for the environment variables ROM_TYPE and TARGET_PHONE
#
#  10.12.2025 v1.3.1.0 /bs
#     the script now prints an error message if there are more or less then one BUILD_ID definitions in the file ./build/core/build_id.mk
#
#  12.12.2025 v1.4.0.0 /bs
#     the script now reads the "real patchlevel" from the file
#        ./vendor/lineage/release/flag_values/${BUILD_ID}/RELEASE_PLATFORM_SECURITY_PATCH.textproto
#      or 
#        ./build/release/flag_values/${BUILD_ID}/RELEASE_PLATFORM_SECURITY_PATCH.textproto
#     if one of them exists
#     the script did not support multiple directories in the parameter -- fixed
#     removed the usage of the environment variable PRINT
#
#  27.12.2025 v1.4.0.1 /bs
#     __TRUE and __FALSE are not set if already defined
#

#h# 
#h# This script works for repositories that were last updated before and after the release of Android 14 QPR2 (in Q1 2024)
#h# If the script is called without a parameter, it must be executed at the top level in a repository tree for AOSP
#h# 
#
#h# Usage: 
#h# 
#h#    print_security_patch [rom_zip_file|repository_tree] [var=value] [...]
#h# 
#h# Known values for the parameter "var=value" (these environment variables can also be defined before starting the script):
#h# 
#h# Parameter                  Description
#h# ---------------------------------------------------------------------------------------
#h# PRINTVARS=0                Print the value for the global variables set by this script
#h# VERBOSE=0                  Turn on verbose messages
#h# DEBUG=0                    execute the script with trace enabled (set -x)
#h# 

# Without the parameter the script prints the security patch level of the repository tree starting at the current working directory
#
# If used like this
#
# . print_security_patch
#
# the script defines these environment variables for the repository tree starting in the current working directory:
#
#    RELEASE_PLATFORM_SECURITY_PATCH
#    REAL_PLATFORM_SECURITY_PATCH
#    BUILD_ID
#    REPO_FORMAT
#
#
# Returncodes:
# 
#    0 - this is a repository for ASOP in new format
#    1 - this is a repository for ASOP in old format
#   98 - script executed by an invalid user
#   99 - show usage helpo
#  100 - the current directory is not the top level of a repository tree for AOSP
#  201 - The file META-INF/com/android/metadata is missing in the ZIP file
#  202 - The file META-INF/com/android/metadata in the ZIP file is empty
#  203 - The statement post-security-patch-level= is missing in the file META-INF/com/android/metadata
#  240 - Can not change the working directory to the directory found in the parameter
#  250 - Invalid parameter
#  251 - Invalid parameter to set a variable
#

# ---------------------------------------------------------------------
# enable "set -x" if requested
#
[ "${DEBUG}"x != ""x ] && set -x

# ---------------------------------------------------------------------
# define constants

[ "${__TRUE}"x = ""x ] &&  typeset  __TRUE=0
[ "${__FALSE}"x = ""x ] && typeset  __FALSE=1

# ---------------------------------------------------------------------
# get the script name and version
#
if [[ $0 == *print_security_patch ]] ; then
  SCRIPT_VERSION="$( grep "#.*/bs" $0 | grep -v grep | tail -1 | awk '{ print $3}' )"
  SCRIPT_NAME="${0##*/}"
else
# this script is sourced in - use the hardcoded values
  SCRIPT_VERSION="v1.4.0.0"
  SCRIPT_NAME="print_security_patchlevel"
fi


# ---------------------------------------------------------------------
# check for the parameter -h or --help
#
if [[ $0 = *print_security_patch ]] ; then
  if [ "$1"x = "-h"x -o  "$1"x = "--help"x ] ; then
    echo "${SCRIPT_NAME} ${SCRIPT_VERSION}"
    grep -E "^#h#" $0 | cut -f2- -d " "
    echo

#    echo "Environment variables supported by this script:"
#    echo
#    for CUR_VAR in $( grep -E "^[A-Z].*:=" $0 | cut -f1 -d "=" ); do
#      echo "${CUR_VAR} - current value is $( eval echo \\\"\$$CUR_VAR\\\" )"    done

    exit 99
  fi
fi


# ---------------------------------------------------------------------
# old build config file 
#
VERSIONS_DEFAULTS_MK="./build/make/core/version_defaults.mk"

# ---------------------------------------------------------------------
# new build config files 
#
BUILD_ID_FILE="./build/core/build_id.mk"

BUILD_RELEASE_DIR="build/release"

RELEASE_CONFIG_MAP_FILE="${BUILD_RELEASE_DIR}/release_config_map.mk"

# ---------------------------------------------------------------------
# variables for the values found in the repository
#
RELEASE_PLATFORM_SECURITY_PATCH=""
BUILD_ID=""
REAL_PLATFORM_SECURITY_PATCH=""

# variables using the values found in the repository
#
REPO_FORMAT=""

# ---------------------------------------------------------------------
# variables for the build command printed by this script

PRINT_GLOBAL_VARIABLES=${__FALSE}
#PRINTVARS=""

GLOBAL_VARIABLES_IN_THE_REPO="RELEASE_PLATFORM_SECURITY_PATCH REAL_PLATFORM_SECURITY_PATCH BUILD_ID REPO_FORMAT "

# undocumented environment variable 
#
SEPARATOR_LINE="# -------------------------------------------------------------------------------------------------------------------------------- "

# ---------------------------------------------------------------------

THISRC=0

# ---------------------------------------------------------------------
# functions
#

function LogMsg {
   echo "$*"
}

function LogInfo {
  if [ "${VERBOSE}"x != ""x ] ; then
    LogMsg "INFO: $*"
  fi
}

function LogWarning {
  LogMsg "WARNING: $*" 
}

function LogError {
  LogMsg "ERROR: $*" >&2
}

# ---------------------------------------------------------------------
# print the security patch level used in repository tree for the Android OS
#
function print_security_patchlevel_of_a_repository {

  [ "${DEBUG}"x != ""x ] && set -x

  typeset THISRC=0
  
  typeset THIS_CONFIG_FILE=""
  
#
# check if the current directory is the top level of a repository for Android
#
  grep  "https://android.googlesource.com" .repo/manifests/default.xml 2>/dev/null >/dev/null
  if [ $? -ne 0 ] ; then
    LogError "The directory \"${PWD}\" is not the top level of a repository for ASOP"
    THISRC=100
  else
  
    if [ -r "${VERSIONS_DEFAULTS_MK}" ]  ;then

# this repository use the format prior to Android 14 QPR2

      REPO_FORMAT="old"

      LogInfo "Reading the values from the source file \"${VERSIONS_DEFAULTS_MK}\" ..."

      RELEASE_PLATFORM_SECURITY_PATCH="$( grep "^[[:space:]]*PLATFORM_SECURITY_PATCH[[:space:]]*:=" "${VERSIONS_DEFAULTS_MK}" 2>/dev/null | awk '{ print $NF }'  )"
     
      BUILD_ID="$( grep "^[[:space:]]*DEFAULT_PLATFORM_VERSION[[:space:]]*:="  "${VERSIONS_DEFAULTS_MK}" | awk '{ print $NF }' )"
      THISRC=1

    elif [ -r  "${BUILD_ID_FILE}" ] ; then

# this repository use the format introduced in Android 14 QPR2

      REPO_FORMAT="new"

      LogInfo "Reading the values from the source file \"${BUILD_ID_FILE}\" ..."

      FULL_BUILD_ID="$( grep "^BUILD_ID=" "${BUILD_ID_FILE}" | cut -f2 -d "=" )"

      i=$( echo "${FULL_BUILD_ID}" | wc -l )
      if [ $i = 0 ] ; then
        LogError "No build id found in the file \"${BUILD_ID_FILE}\" "
      elif [ $i -ne 1 ] ; then
        LogMsg ""
        LogError "More than one build id found in the file \"${BUILD_ID_FILE}\":"
        LogMsg "-" "$( grep -v "^#"  ${BUILD_ID_FILE} )"
        LogMsg "Using the last build id from the file \"${BUILD_ID_FILE}\" "
        FULL_BUILD_ID="$( echo "${FULL_BUILD_ID}" | tail -1 )"
      fi

      BUILD_ID="$( echo "${FULL_BUILD_ID%%.*}"  | tr "[A-Z]" "[a-z]" )"
      PATCH_DATE="$( echo "${FULL_BUILD_ID}"  | cut -f2 -d"." )"

      VENDOR_RELEASE_PLATFORM_SECURITY_PATCH_FILE="./vendor/lineage/release/flag_values/${BUILD_ID}/RELEASE_PLATFORM_SECURITY_PATCH.textproto"
      CUR_RELEASE_PLATFORM_SECURITY_PATCH_FILE="./build/release/flag_values/${BUILD_ID}/RELEASE_PLATFORM_SECURITY_PATCH.textproto"

      [ "${PATCH_DATE}"x != ""x ] && RELEASE_PLATFORM_SECURITY_PATCH="20$( echo "${PATCH_DATE}" | cut -c1,2 )-$( echo "${PATCH_DATE}" | cut -c3,4 )-$( echo "${PATCH_DATE}" | cut -c5,6 )"

      if [ "${REAL_PLATFORM_SECURITY_PATCH}"x = ""x ] ; then        
      
        THIS_CONFIG_FILE="${VENDOR_RELEASE_PLATFORM_SECURITY_PATCH_FILE}"

        if [ -r  "${THIS_CONFIG_FILE}" ] ; then
          LogInfo "The file\"${THIS_CONFIG_FILE}\" exists"
          REAL_PLATFORM_SECURITY_PATCH="$( sed -n "/name: \"RELEASE_PLATFORM_SECURITY_PATCH\"/,/}/p" "${THIS_CONFIG_FILE}"  | grep string_value | cut -f2 -d '"' )"
          LogInfo "The patchdate in the file \"${THIS_CONFIG_FILE}\" is \"${REAL_PLATFORM_SECURITY_PATCH}\""
        
        fi
      fi


      if [ "${REAL_PLATFORM_SECURITY_PATCH}"x = ""x ] ; then                         

        THIS_CONFIG_FILE="${CUR_RELEASE_PLATFORM_SECURITY_PATCH_FILE}"
        if [ -r  "${THIS_CONFIG_FILE}" ] ; then
          LogInfo "The file\"${THIS_CONFIG_FILE}\" exists"
          REAL_PLATFORM_SECURITY_PATCH="$( sed -n "/name: \"RELEASE_PLATFORM_SECURITY_PATCH\"/,/}/p" "${THIS_CONFIG_FILE}"  | grep string_value | cut -f2 -d '"' )"
          LogInfo "The patchdate in the file \"${THIS_CONFIG_FILE}\" is \"${REAL_PLATFORM_SECURITY_PATCH}\""
        fi         
      fi
    elif [ -r "${RELEASE_CONFIG_MAP_FILE}" ] ; then
      REPO_FORMAT="new"

      SCL_FILE="$( grep declare-release-config "${RELEASE_CONFIG_MAP_FILE}" | tr "()" "  " | awk '{ print $NF }' )"

      LogInfo "Reading the build id from the source file \"${RELEASE_CONFIG_MAP_FILE}\" ..."

      BUILD_ID="$( grep declare-release-config "${RELEASE_CONFIG_MAP_FILE}"  | tr "," " " | awk '{ print $3}' )"
      THISRC=0
    
      if [ "${SCL_FILE}"x != ""x ] ; then

        SCL_FILE="${BUILD_RELEASE_DIR}/${SCL_FILE}"
        if [ -r "${SCL_FILE}" ] ; then
          LogInfo "Reading the security patchlevel from the source file \"${SCL_FILE}\" ..."
          RELEASE_PLATFORM_SECURITY_PATCH="$( grep RELEASE_PLATFORM_SECURITY_PATCH  "${SCL_FILE}" | cut -f4 -d '"' )"
        fi
      fi
    fi


    if [ ${OUTPUT_ON_ONE_LINE} != ${__TRUE} ] ; then
      if [ "${REPO_FORMAT}"x != ""x ] ; then
        LogMsg "The repository uses the ${REPO_FORMAT} definitions"
      fi
      
      if [ "${RELEASE_PLATFORM_SECURITY_PATCH}"x != ""x ] ; then
        LogMsg "The security patch in the BUILD_ID is \"${RELEASE_PLATFORM_SECURITY_PATCH}\" "
      else
        LogMsg "Can not detect the current security patch"
      fi

      if [ "${REAL_PLATFORM_SECURITY_PATCH}"x != ""x ] ; then
        LogMsg "The real security patch is \"${REAL_PLATFORM_SECURITY_PATCH}\" "
      elif  [ "${REPO_FORMAT}"x != "old"x ] ; then
        LogInfo "Can not detect the real security patch"
      fi

      if [ "${BUILD_ID}"x != ""x ] ; then
        LogMsg "The current build_id is \"${BUILD_ID}\" "
      else
        LogMsg "Can not detect the current build id"
      fi
    else
      LogMsg "Repository: ${PWD}, Build ID Patch Level: ${RELEASE_PLATFORM_SECURITY_PATCH}, Real Patch Level: ${REAL_PLATFORM_SECURITY_PATCH}, Build ID: ${BUILD_ID}, Repository format: ${REPO_FORMAT} "
    fi
  fi

  return ${THISRC}
}


# ---------------------------------------------------------------------
# print the security patch level used to build an Android ROM zip file
#
function print_security_patchlevel_in_an_image_zip_file {

  [ "${DEBUG}"x != ""x ] && set -x

  typeset THISRC=0

  typeset CUR_ZIP_FILE="$1"
   
  typeset METADATA_FILE="META-INF/com/android/metadata"
  typeset METADATA=""
  typeset POST_SECURITY_PATCH_LEVEL=""
  
  METADATA="$( unzip -p "${CUR_ZIP_FILE}" "${METADATA_FILE}" 2>&1 )"
  if [ $? -ne 0 ] ; then
    LogError "${METADATA}"
    LogError "The file \"${METADATA_FILE}\" is missing in the ZIP file \"${CUR_ZIP_FILE}\" "
    THISRC=201
  elif [ "${METADATA}"x = ""x ] ; then
    LogError "The file \"${METADATA_FILE}\" in the ZIP file \"${CUR_ZIP_FILE}\" is empty"
    THISRC=202
  else
    POST_SECURITY_PATCH_LEVEL="$( echo "${METADATA}" | grep -E "^post-security-patch-level=" | cut -f2- -d "=" )"
    if [ "${POST_SECURITY_PATCH_LEVEL}"x = ""x ] ; then
      LogError "The statement \"post-security-patch-level=<date>\" is missing in the file \"${METADATA_FILE}\" from the ZIP file \"${CUR_ZIP_FILE}\" "
      THISRC=203
    else
      LogMsg "ZIP File: ${CUR_ZIP_FILE}, Patch Level: ${POST_SECURITY_PATCH_LEVEL}"
      THISRC=0
    fi
  fi
  
  return ${THISRC}
}

# ---------------------------------------------------------------------
# main function
#

LogInfo "${SCRIPT_NAME} ${SCRIPT_VERSION}"

[ ${PRINT_GLOBAL_VARIABLES} != ${__FALSE} ] && PRINTVARS="0"

NO_OF_REPOS=0


PARAMETER_FOUND=${__FALSE}
for CUR_PARAMETER in $* ; do
  if [[ ${CUR_PARAMETER} != *=* ]] ; then
    PARAMETER_FOUND=${__TRUE}
    (( NO_OF_REPOS =  NO_OF_REPOS + 1 ))
  fi
done

LIST_OF_PARAMETERS="$*"

if [ ${PARAMETER_FOUND} = ${__FALSE} ] ; then
  LogMsg "Retrieving the security patch level for the repository at \"${PWD}\" ..."
  LIST_OF_PARAMETERS="${LIST_OF_PARAMETERS} ${PWD}"
  OUTPUT_ON_ONE_LINE=${OUTPUT_ON_ONE_LINE:=${__FALSE}}
else
  OUTPUT_ON_ONE_LINE=${OUTPUT_ON_ONE_LINE:=${__TRUE}}
fi


if [ 0 = 0 ] ; then
  
  CUR_PWD="${PWD}"
  for CUR_PARAMETER in ${LIST_OF_PARAMETERS} ; do

    cd "${CUR_PWD}"
    
    RELEASE_PLATFORM_SECURITY_PATCH=""
    BUILD_ID=""
    REAL_PLATFORM_SECURITY_PATCH=""
    REPO_FORMAT=""

    if [[ ${CUR_PARAMETER} = *=* ]] ; then
      LogInfo "Executing now \"${CUR_PARAMETER}\" ..."
      eval "${CUR_PARAMETER}" 
      if [ $? -ne 0 ] ; then
        LogError "Error executing \"${CUR_PARAMETER}\" "
        exit 251
      else
        continue
      fi
    fi
  
    if [ -d "${CUR_PARAMETER}" ] ; then

      if [ "${PRINTVARS}"x != ""x ] ; then
        [ ${NO_OF_REPOS} != 0 ] && LogMsg "${SEPARATOR_LINE}"
      fi

      cd "${CUR_PARAMETER}"
      if [ $? -ne 0 ] ; then
        LogError "Can not change the working directory to \"${CUR_PARAMETER}\" "
        THISRC=240
      else
   
        print_security_patchlevel_of_a_repository
        TEMPRC=$?
        [ ${TEMPRC} -gt ${THISRC} ] && THISRC=${TEMPRC}
        if [ ${TEMPRC} -le 1 ] ; then
          
          if [ "${PRINTVARS}"x != ""x  ] ; then
  
            LogMsg ""
            LogMsg "The values for the global variables found in the repos in the directory \"${CUR_PARAMETER}\" are:"
            LogMsg ""

            for CUR_VAR in ${GLOBAL_VARIABLES_IN_THE_REPO} ; do
              eval "CUR_VALUE=\"\$${CUR_VAR}\""
              LogMsg "${CUR_VAR}=\"${CUR_VALUE}\""
            done

            LogMsg ""
          fi
        fi
      fi
    elif [ -f ${CUR_PARAMETER} ] ; then
      LogInfo "Retrieving the security patch level used in the zip fole \"${CUR_PARAMETER}\" ..."
      print_security_patchlevel_in_an_image_zip_file "${CUR_PARAMETER}"
      TEMPRC=$?
      [ ${TEMPRC} -gt ${THISRC} ] && THISRC=${TEMPRC}
    else
      LogError "The parameter \"${CUR_PARAMETER}\" is neither a repository directory nor an image file"
      THISRC=250
    fi
  done
fi

# ---------------------------------------------------------------------



# ---------------------------------------------------------------------


# ---------------------------------------------------------------------


if [[ $0 = *print_security_patch ]] ; then
  exit ${THISRC}
fi

# ---------------------------------------------------------------------
