#!/usr/bin/env sh
# set -x

# 
# (C) 2015-2020 HEWLETT PACKARD ENTERPRISE
# THIS SOURCE CODE CONTAINS TRADE SECRETS OF HEWLETT PACKARD ENTERPRISE.
# HPE CONFIDENTIAL - NEED TO KNOW REQUIRED
# PROPRIETARY NOTE
#
# This software contains information confidential and proprietary to
# Hewlett Packard Enterprise. It shall not be reproduced
# in whole or in part, or transferred to other documents, or disclosed
# to third parties, or used for any purpose other than that for which it
# it was legally obtained, without the prior written consent of
# Hewlett Packard Enterprise.
#
# Copyright 2015-2020 Hewlett Packard Enterprise.
#
# Description:
#   This script will have common functions which are used to perform Device Inventory, 
#   extract/install the utility and flash the FW using the Utility
#

# Covert all upper case character to lower case characters.
upperToLower() {
  lowercase=$(echo $1 | awk 'BEGIN { getline; print $1=tolower($1) }')
}

_LOGFILE=/var/cpq/Component.log

# Return codes
SUCCESS_NO_REBOOT=0
SUCCESS_WITH_REBOOT=1
INSTALL_NOT_ATTEMPTED=3
INSTALL_FAILED=7

# Variables
SC_RESULT_FAIL=0
SC_RESULT_PASS=0
discoveryFlag=0
forceFlag=0
silentFlag=0
downgradeFlag=0
rewriteFlag=0

ARGUS=$@
NARGS=$#
_VER=
_REV=
_COMP=
_DESCRIPT=
_FILENAME=
_COMPVERBOSE=

for arg in "$@"
do
    case $arg in
        -s|--silent)
            silentFlag=1
            shift
        ;;
        -f|--force)
            forceFlag=1
            #echo "force flag enabled"
            shift
        ;;
        -g|--downgrade)
            downgradeFlag=1
            #echo "downgrade flag enabled"
            shift
        ;;
        -e|--rewrite)
            rewriteFlag=1
            #echo "rewrite flag enabled"
            shift
        ;;
        -d|--discovery)
            discoveryFlag=1
            xmlFile="$2"
            shift
        ;;
        --default)
            echo "Invalid Argument!"
            exit $INSTALL_NOT_ATTEMPTED
        ;;
    esac
done

# Write information to stdout and the log file.
output_CmpLog() {
  echo "$1" >> $_LOGFILE
  if [ 1 -eq $silentFlag ]; then
    echo "[$(date '+%a %d %H:%M:%S')] $1" >> $_COMP_LOGFILE
  else
    echo "[$(date '+%a %d %H:%M:%S')] $1" | tee -a $_COMP_LOGFILE
  fi
}

# Write information to discovery xml file.
printXML() {
  if [ -z $xmlFile ]; then
    xmlFile="/var/cpq/${_package}_disc.xml"
  fi
  if [ ! -x $1 ]; then
    echo -e $1 "$2" >> $xmlFile
  else
    echo "$2" >> $xmlFile
  fi
}

file="unknown"
# Search the specified directory (i.e. $1) for a specific file or file type (i.e. $2).
directory_listing() {
  file=""
  file=$(find "$1" -type f -iname "$2" | sed -e 's/^\.\///' 2> /dev/null)  
  number_of_files=$(echo $file | awk ' { print split($0, ARRAY_OF_FILES, "[[:space:]]+") } ')
  # Verify that the file variable is not an empty string.
  if [ 0 -eq $number_of_files ]; then
    output_CmpLog "Error: Could not find any Smart Component XML file, please install at least one Smart Component package and re-run the script."
    output_CmpLog "Script Exiting."
    output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
    exit $INSTALL_NOT_ATTEMPTED
  fi
  _VER=`grep \<version $file | awk -F = '{print $2}' | awk '{gsub(/"/, "", $1); print $1}' | head -n 1`
  _COMP=`grep \<filename\> $file|awk -F '>' '{print $2}'|awk -F '<' '{print $1}'| awk '{gsub(/"/, "", $1); print $1}'`
  _DESCRIPT=`grep \<alt_name_xlate\ lang\=\"en\" $file|awk -F '>' '{print $2}'|awk -F '<' '{print $1}'|sed -e 's/\"//g'|tail -n 1`
  _FILENAME="${_COMP%.*}"
  _date=$(date '+%Y_%m_%d_%H_%M_%S')
  _package=$(echo $_COMP | cut -d'.' -f 1)
  _COMP_LOGFILE="/var/cpq/${_package}_${_date}.log"
  
  output_CmpLog "Run with $NARGS Command Arguments(s): $ARGUS"
  output_CmpLog ""
  output_CmpLog "$_COMP - $_DESCRIPT"
  output_CmpLog "    Component Version: $_VER"
}

getCmpXML() {
  directory_listing "." "[cC][Pp]0*.xml"
}

# This function Check to see if the file is readable.
isFileReadable() {
  #echo "isFileReadable"
  if [ ! -r $1 ]; then
    output_CmpLog "Error: $1 file is not readable, please make the file readable and re-run the script."
    output_CmpLog "Script exiting."
    output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
    exit $INSTALL_NOT_ATTEMPTED
  fi
}

getOSPlateform(){
  OS=$(uname -o)
  OSVER=$(uname -r)
  output_CmpLog "OS version: $OS $OSVER"
}

locateMNVUtil(){
  mnvutil="$PWD/mnv_cli"
  if [ -e "$mnvutil" ]; then
    if [ ! -x "$mnvutil" ]; then
      chmod ug+x ${mnvutil}
    fi
    if [ ! -x "$mnvutil" ]; then
      output_CmpLog "Error: Cannot change the execute permission of mnv_cli"
      output_CmpLog "Script Exiting.."
      output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
      exit $INSTALL_NOT_ATTEMPTED
    fi
  else
    output_CmpLog "Error: Cannot find mnv_cli Utility"
    exit $INSTALL_NOT_ATTEMPTED
  fi
}

getBootCtrlCnt(){
  # Find the number of supported Boot Controllers on the system
  controller_list=$("$mnvutil" adapter --list)

  n_controllers=$(echo "$controller_list" | grep -Eio "total number of nvme controllers: [0-9]+" | grep -Eo "[0-9]+")
  if [ -z $n_controllers ]; then
    if [ 1 -eq $discoveryFlag ]; then
      printXML "" "<hp_rom_discovery version=\"2.0.2.0\">"
      printXML '\t' "<type value=\"firmware:sd:\" />"
      printXML '\t' "<alt_name value=\"$_DESCRIPT\" />"
      printXML '\t' "<version value=\"$xml_file_firmware_version\" />"
      printXML '\t' "<takes_effect value=\"deferred\" />"
      printXML '\t' "<devices>"
      printXML '\t' "</devices>"
      printXML "" "</hp_rom_discovery>"
      exit $INSTALL_NOT_ATTEMPTED
    fi
    output_CmpLog "Error: No Supported Boot Controllers found."
    output_CmpLog "Script Exiting."
    output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
    exit $INSTALL_NOT_ATTEMPTED
  else
    output_CmpLog "Number of Boot Controllers found: $n_controllers"
  fi
}

queryDeviceIDs(){
  subvendorid=$("$mnvutil" info -o hba | grep -i "SVID:" | awk '{$2=$2};1' | awk '{print $2;exit;}')
  subdeviceid=$("$mnvutil" info -o hba | grep -i "SDID:" | awk '{$2=$2};1' | awk '{print $2;exit;}')
  cntrl_id=$("$mnvutil" info -o hba | grep -i "Slot ID:" | awk '{$3=$3};1' | awk '{print $3;exit;}')  
}

checkBootController(){
  # Method to check if the arguementts received are valid Boot Controller Device IDs
  if [ "$1" = "0x1590" ]; then
    if [[ "$2" == "0x2f6" || "$2" == "0x30e" || "$2" == "0x30f" || "$2" == "0x310" ]]; then
      return 0
    fi
  fi
  return 6
}

queryUpdateAction(){
  if [ "$controller_fw" = "$xml_file_firmware_version" ]; then
    perform_action="rewrite"
  elif [ "$controller_fw" \< "$xml_file_firmware_version" ]; then
    perform_action="upgrade"
  else
    perform_action="downgrade"
  fi
}

getBootCntrlFirmwareVersion(){
  # Get the Boot controller's firmware version.
  controller_fw=$("$mnvutil" info -o hba | grep -i "Firmware Version:" | awk '{$3=$3};1' | awk '{print $3;exit;}')
  if [ -z $controller_fw ]; then
    output_CmpLog "Error: Could not obtain the current Firmware Version of the Boot Controller"
  fi
}

deviceDiscovery(){
  # Discover the devices installed on the system
  output_CmpLog "Generating discovery file at $xmlFile"
  printXML "" "<hp_rom_discovery version=\"2.0.2.0\">"
  printXML '\t' "<type value=\"firmware:sd:\" />"
  printXML '\t' "<alt_name value=\"$_DESCRIPT\" />"
  printXML '\t' "<version value=\"$xml_file_firmware_version\" />"
  printXML '\t' "<takes_effect value=\"deferred\" />"
  printXML '\t' "<devices>"

  queryDeviceIDs
  output_CmpLog "Boot Controller ID: $cntrl_id"  
  checkBootController $subvendorid $subdeviceid
  isbootctrl=$?
  if [ $isbootctrl -eq 0 ]; then
    getBootCntrlFirmwareVersion
    queryUpdateAction
    output_CmpLog "Discovered SubVendorID: $subvendorid, SubDeviceID: $subdeviceid"
    printXML '\t\t' "<device id=\"$cntrl_id\">"
    printXML '\t\t\t' "<controller_id value=\"$cntrl_id\" />"
    printXML '\t\t\t' "<product_id value=\"`echo ${subdeviceid//x} | awk '{ print toupper($0) }'`1590\" />"
    printXML '\t\t\t' "<fw_item>"
    printXML '\t\t\t\t' "<type value=\"firmware\" />"
    printXML '\t\t\t\t' "<firmware_id value=\"\" />"
    printXML '\t\t\t\t' "<takes_effect value=\"deferred\" />"
    printXML '\t\t\t\t' "<version value=\"$xml_file_firmware_version\" />"
    printXML '\t\t\t\t' "<active_version value=\"$controller_fw\" />"
    printXML '\t\t\t\t' "<action value=\"$perform_action\" />"
    printXML '\t\t\t\t' "<duration value=\"6\" />"
    printXML '\t\t\t\t' "<shared value=\"no\" />"
    printXML '\t\t\t' "</fw_item>"     
    printXML '\t\t' "</device>"
  fi
  
  printXML '\t' "</devices>"
  printXML "</hp_rom_discovery>"
  output_CmpLog "============ Summary ============"    
  output_CmpLog "Smart Component Finished"
  output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
  output_CmpLog "Device discovery complete"
  output_CmpLog "Update not attempted."
  exit $INSTALL_NOT_ATTEMPTED
}

flashDevices(){
  queryDeviceIDs
  checkBootController $subvendorid $subdeviceid
  isbootctrl=$?
  if [ $isbootctrl -eq 0 ]; then
    output_CmpLog "Boot Controller ID: $cntrl_id"
    output_CmpLog "Discovered SubVendorID: $subvendorid, SubDeviceID: $subdeviceid"
    getBootCntrlFirmwareVersion
    output_CmpLog "Firmware binary: $PWD/$firmware_binary"
    flash "$cntrl_id" "$PWD/$firmware_binary"
  fi
}

flash(){
  getBootCntrlFirmwareVersion
  queryUpdateAction

  # Formatting the log based on the action performed (upgrade, downgrade or rewrite)
  if [ "$perform_action" = "rewrite" ]; then
    if [[ 0 -eq $forceFlag && 0 -eq $rewriteFlag ]]; then
      output_CmpLog "Installation NOT attempted on Boot Controller ID: $1"
      output_CmpLog "Device is up-to-date"
      return
    fi
    output_CmpLog "Attempting to rewrite the FW $xml_file_firmware_version on Boot Controller ID: $1"
  elif [ "$perform_action" = "downgrade" ]; then
    if [[ 0 -eq $forceFlag && 0 -eq $downgradeFlag ]]; then
      output_CmpLog "Installation NOT attempted on Boot Controller ID: $1"
      output_CmpLog "Device has the latest Firmware"
      return
    fi
    output_CmpLog "Attempting to downgrade the FW from $controller_fw to $xml_file_firmware_version on Boot Controller ID: $1"
  else
    output_CmpLog "Attempting to upgrade the FW from $controller_fw to $xml_file_firmware_version on Boot Controller ID: $1" 
  fi
  # Flashing the Boot Controller
  output_CmpLog "This could take several minutes..."
  output_CmpLog "Firmware flashing started..."
  flash_status=$("$mnvutil" flash -o hba -f $2 -c 1 -s 0)
  retCode=$?
  if echo "$flash_status" | tail -1 | grep -qi "Flash NVMe Controller firmware success"; then
    output_CmpLog "Successfully flashed the firmware on Boot Controller ID: $1"
    output_CmpLog "Please reboot the system to activate the Firmware!"
    SC_RESULT_PASS=1
  else
    output_CmpLog "Error: Failed to flash the firmware on Boot Controller ID: $1"
    SC_RESULT_FAIL=1
  fi
  output_CmpLog "==============================================================================================="
}

parseXML() {
  xml_file=$file
  isFileReadable $PWD/$xml_file
  # Extract the name of the firmware binary file from the XML file.
  firmware_binary=$(grep -o -E "<FileName>(.*)</FileName>" "$PWD/$xml_file" | sed 's/FileName//g' | sed 's/<//g' | sed 's/>//g' | sed 's/\///g' | head -1)
  if [ -z $firmware_binary ]; then
    output_CmpLog "No firmware binary file was found"
    output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
    exit $INSTALL_NOT_ATTEMPTED
  fi
  isFileReadable $PWD/$firmware_binary
  # Read from the smart component's xml file and obtain the firmware version.
  xml_file_firmware_version=$(grep 'version value=' "$xml_file" | head -1 | awk '{print $2}' | sed -e 's/value="\(.*\)"/\1/')    
}

main(){
  getCmpXML
  parseXML
  getOSPlateform
  locateMNVUtil
  getBootCtrlCnt
  if [ 1 -eq $discoveryFlag ]; then
    deviceDiscovery
  else
    flashDevices
    output_CmpLog "============================== Summary ====================================="
    output_CmpLog "Smart Component Finished"

    if [ 1 -eq $SC_RESULT_FAIL ]; then
      output_CmpLog "Exit Status: $INSTALL_FAILED"
      exit $INSTALL_FAILED
    elif [ 1 -eq $SC_RESULT_PASS ]; then
      output_CmpLog "Exit Status: $SUCCESS_WITH_REBOOT"
      exit $SUCCESS_WITH_REBOOT
    else
      output_CmpLog "Update Not attempted (or interrupted). All selected devices are either up-to-date or have newer versions installed or Hardware not detected"
      output_CmpLog "Exit Status: $INSTALL_NOT_ATTEMPTED"
      exit $INSTALL_NOT_ATTEMPTED
    fi
  fi
}

main