#!/bin/bash

# FixVPD script to identify and allow correction of VPD issues on S10/S30
# 02-Jan-2018: Initial version 1.0

# Pull in shared function definitions
SHAREDFUNC=./sharedfunc
if [[ ! -f $SHAREDFUNC ]]; then
    echo
    echo "ERROR: Shared functions file $SHAREDFUNC not found"
    exit 1
fi
. $SHAREDFUNC

# Function to show usage message and help
# No arguments
# No return
Usage()
{
    echo "Usage: $0 [-a][-h]"
#    echo "Usage: $0 [-a][-f][-h][-r]"
    echo
    echo "    With no arguments, the script checks all enclosures for known VPD corruption issues"
    echo
    echo "    -a: Automatically fix issues found without interaction"
    echo
#   echo "    -f: Allow user to interactively fix issues found"
#   echo
    echo "    -h: Display this command usage information"
    echo
#   echo "    -r: Allow reset of the expanders to pick up the VPD changes after reflashing the VPD"
#   echo
    exit 1
}

# Function to check a VPD output file for critical fields
# $1 = File path/name to check (assumes it exists)
# RESULT = CORRUPT if critical fields have invalid values
CheckVPDCriticalFields()
{
    RESULT=
    FOUNDERROR=
    FIELDS="\
        ChassisInfo:PartNumber:EXACT:0:NDS-4600-JD\
        ChassisInfo:SerialNumber:ALPHANUMCAP:16:VARIABLE\
        BoardInfo:SerialNumber:ALPHANUMCAP:16:PREVIOUS\
        BoardInfo:ProductName:EXACT:0:NDS-4600-BB\
        BoardInfo:PartNumber:PARTIAL:0:TCA-00341-\
        BoardInfo:Manufacturer:EXACT:0:SANMINA\
        ProductInfo:Name:EXACT:0:NDS-4600-JD\
        ProductInfo:Manufacturer:EXACT:0:NEWISYS\
        ProductInfo:Version:EXACT:4:0001\
        EnclosureConfig:EnclosureBaseSasAddress:HEXCAPNZNF:16:VARIABLE\
        IPConfig:MacAddress:HEXCAPNZNF:12:VARIABLE\
    "
    if (( $# == 1 )); then
        if [[ ! -f $1 ]]; then
            echo "CheckVPDCriticalFields: File $FILE Not Found"
            exit 2
        fi
        for FIELD in $FIELDS; do
            SECTION=${FIELD%%:*}
            FIELD=${FIELD#$SECTION:}
            ITEM=${FIELD%%:*}
            FIELD=${FIELD#$ITEM:}
            CHECK=${FIELD%%:*}
            FIELD=${FIELD#$CHECK:}
            LENGTH=${FIELD%%:*}
            FIELD=${FIELD#$LENGTH:}
            EXPVALUE=$FIELD
            VALUE=$(grep $SECTION $1 | grep " $ITEM " | awk '{print $7}' | sed -e "s/\"//g")
            case $CHECK in
                EXACT)
                    if [[ "$VALUE" != "$EXPVALUE" ]]; then
                        echo "File $1: Invalid $SECTION/$ITEM value: $VALUE - Should be $EXPVALUE"
                        FOUNDERROR=TRUE
                    fi
                    ;;
                PARTIAL)
                    if [[ "${VALUE%${VALUE#$EXPVALUE}}" != "$EXPVALUE" ]]; then
                        echo "File $1: Invalid $SECTION/$ITEM value: $VALUE - Should start with $EXPVALUE"
                        FOUNDERROR=TRUE
                    fi
                    ;;
                *)
                    if [[ "$VALUE" != "" ]]; then
                        CheckString $LENGTH $CHECK $VALUE
                        if [ "$LENGTHCHECK" != "TRUE" -o "$VALIDATIONCHECK" != "TRUE" ]; then
                            echo "File $1: Invalid $SECTION/$ITEM value: $VALUE - Invalid Length And/Or Invalid Characters"
                            FOUNDERROR=TRUE
                        fi
                    else
                        echo "File $1: Invalid contents, could not find field $SECTION/$ITEM"
                        FOUNDERROR=TRUE
                    fi
                    ;;
            esac
            if [[ $EXPVALUE == PREVIOUS ]]; then
                if [[ "$VALUE" != "$PREVVALUE" ]]; then
                    echo "File $1: Invalid $SECTION/$ITEM value: Should have matched $PREVSECTION/$PREVITEM"
                    FOUNDERROR=TRUE
                fi
            fi
            DEFAULTVALUEERROR=
            if [ $SECTION == ChassisInfo -o $SECTION == BoardInfo -a $ITEM == SerialNumber -a "$VALUE" != "" ]; then
                if [ $VALUE == $DEFAULTS10SERIAL -o $VALUE == $DEFAULTS30SERIAL ]; then
                    DEFAULTVALUEERROR=TRUE
                fi
            fi
            if [ $SECTION == EnclosureConfig -a $ITEM == EnclosureBaseSasAddress -a "$VALUE" != "" ]; then
                if [ $VALUE == $DEFAULTS10BASESAS -o $VALUE == $DEFAULTS30BASESAS ]; then
                    DEFAULTVALUEERROR=TRUE
                fi
            fi
            if [ $SECTION == IPConfig -a $ITEM == MacAddress -a "$VALUE" != "" ]; then
                if [ $VALUE == $DEFAULTS10MACADDRA -o $VALUE == $DEFAULTS10MACADDRB -o $VALUE == $DEFAULTS30MACADDRA -o $VALUE == $DEFAULTS30MACADDRB ]; then
                    DEFAULTVALUEERROR=TRUE
                fi
            fi
            if [[ "$DEFAULTVALUEERROR" == TRUE ]]; then
                echo "File $1: Invalid $SECTION/$ITEM value: $VALUE - Non-allowed Default"
                FOUNDERROR=TRUE
            fi
            PREVSECTION=$SECTION
            PREVITEM=$ITEM
            PREVVALUE=$VALUE
        done
        if [[ "$FOUNDERROR" == TRUE ]]; then
            RESULT=CORRUPT
        fi
    else
        echo "Invalid CheckVPDCriticalFields Function Call"
        exit 2
    fi
}

# Function to check a VPD output file for possible 0xFF corruption
# $1 = File path/name to check (assumes it exists)
# RESULT = CORRUPT if FF corruption is found or empty if okay
FAILCOUNT=24
CheckVPDForFF()
{
    RESULT=
    COUNT=0
    if (( $# == 1 )); then
        if [[ ! -f $1 ]]; then
            echo "CheckVPDForFF: File $FILE Not Found"
            exit 2
        fi
        DATASET=$(grep "^[0-9 ][0-9 ][0-9][0-9]*\./" $1 | grep -v checksum | awk '{print $7}' | sed -e "s/\"//g")
        for DATA in $DATASET; do
            while (( ${#DATA} >= 2 ))
            do
                BYTE=${DATA:0:2}
                DATA=${DATA#$BYTE}
                if [[ $BYTE == FF ]]; then
                    (( COUNT++ ))
                else
                    COUNT=0
                fi
                if (( COUNT == FAILCOUNT )); then
                    break
                fi
            done
            if (( COUNT == FAILCOUNT )); then
                break
            fi
        done
        if (( COUNT == FAILCOUNT )); then
            echo "File $1 contains 0xFF block corruption"
            RESULT=CORRUPT
        fi
    else
        echo "Invalid CheckVPDForFF Function Call"
        exit 2
    fi
}

# Function to check a list of VPD output files for various types of corruption
# $@ = List of path/names to check (expected to be an even number, 2 per enclosure)
# RESULT = List of VPD output files with corruption
CheckVPDFiles()
{
    FILESET=
    PREVFILE=
    LASTADDED=
    for FILE in $@; do
        ADDFILE=
        ADDPREV=
        if [[ ! -f $1 ]]; then
            echo "CheckVPDFiles: File $FILE Not Found"
            exit 2
        fi
        CheckVPDCriticalFields $FILE
        if [[ "$RESULT" != "" ]]; then
            ADDFILE=TRUE
        fi
        CheckVPDForFF $FILE
        if [[ "$RESULT" != "" ]]; then
            ADDFILE=TRUE
        fi
        if [[ "$PREVFILE" != "" ]]; then
            CheckVPDCopiesMatch $PREVFILE $FILE
            if [[ "$RESULT" != "" ]]; then
                ADDPREV=TRUE
                ADDFILE=TRUE
            fi
        fi
        if [ "$ADDPREV" != "" -a "$PREVFILE" != "$LASTADDED" ]; then
            FILESET="$FILESET $PREVFILE"
        fi
        if [[ "$ADDFILE" != "" ]]; then
            FILESET="$FILESET $FILE"
            LASTADDED=$FILE
        fi
        if [[ "$PREVFILE" != "" ]]; then
            PREVFILE=
        else
            PREVFILE=$FILE
        fi
    done
    RESULT=$FILESET
}

# Function to re-read the VPD copies for an enclosure and ensure they are identical
# $1 = Enclosure SCSI generic device name
# RESULT = CORRUPT if the two copies do not match or empty if they do
RecheckEnclosureVPDCopies()
{
    RESULT=
    if (( $# == 1 )); then
        echo "Reading Rewritten Enclosure $ENCLOSURE VPD-1..."
        ReadFromVPDEEPROM $ENCLOSURE 1 dev-${ENCLOSURE#/dev/}-VPD1-2.txt
        echo "Reading Rewritten Enclosure $ENCLOSURE VPD-2..."
        ReadFromVPDEEPROM $ENCLOSURE 2 dev-${ENCLOSURE#/dev/}-VPD2-2.txt
        CheckVPDCopiesMatch dev-${ENCLOSURE#/dev/}-VPD1-2.txt dev-${ENCLOSURE#/dev/}-VPD2-2.txt
        if [[ "$RESULT" != "" ]]; then
            RESULT=CORRUPT
        fi
    else
        echo "Invalid RecheckEnclosureVPDCopies Function Call"
        exit 2
    fi
}

# Function to compare a value from VPD1 and VPD2 and, if possible, choose the right one to use
# $1 = VPD value 1
# $2 = VPD value 2
# $3 = Length which should be matched
# $4 = Validation type (Limited values are supported, see UserInput function for specifics)
# $5 = Default value 1 for VPD item
# $6 = Default value 2 for VPD item
# RESULT = Empty if no value was valid or a value if at least one was varifyably okay
SelectProperVPDValue()
{
    RESULT=
    if (( $# > 4 )); then
        if [[ "$1" != "" ]]; then
            CheckString $3 $4 $1
            if [ "$LENGTHCHECK" == "TRUE" -a "$VALIDATIONCHECK" == "TRUE" -a "$1" != "$5" -a "$1" != "$6" ]; then
                if [[ "$2" != "" ]]; then
                    CheckString $3 $4 $2
                    if [ "$LENGTHCHECK" == "TRUE" -a "$VALIDATIONCHECK" == "TRUE" -a "$2" != "$5" -a "$2" != "$6" ]; then
                        if [[ "$1" == "$2" ]]; then
                            RESULT=$1
                        fi
                    else
                        RESULT=$1
                    fi
                fi
            else
                if [[ "$2" != "" ]]; then
                    CheckString $3 $4 $2
                    if [ "$LENGTHCHECK" == "TRUE" -a "$VALIDATIONCHECK" == "TRUE" ]; then
                        if [ "$2" != "$5" -a "$2" != "$6" ]; then
                            RESULT=$2
                        fi
                    fi
                fi
            fi
        fi
    else
        echo "Invalid SelectProperVPDValue Function Call"
        exit 2
    fi
}

# ==============================
# Main program logic begins here
# ==============================

# Perform argument processing
AUTOMATIC=
FIXISSUES=
RESETEXPANDERS=
while getopts "afhr" OPT; do
    case $OPT in
        a)
            AUTOMATIC=TRUE
            FIXISSUES=TRUE
            ;;
        f)
            FIXISSUES=TRUE
            ;;
        h)
            Usage
            ;;
        r)
            RESETEXPANDERS=TRUE
            ;;
        \?)
            Usage
            ;;
    esac
done

# Determine system type and set global variables
DetermineSystemType TRUE
SYSTEMTYPE=$RESULT

# If we have been asked to interactively fix the VPD(s) or reset SAS expanders, ask again
if [ "$AUTOMATIC" == "" -a "$FIXISSUES" == TRUE -o "$RESETEXPANDERS" == TRUE ]; then
    echo
    echo "================================= WARNING ================================="
    echo "=  USING INTERACTIVE MODE OR RESETTING SAS EXPANDERS CAN CAUSE SYSTEM     ="
    echo "=  OUTAGES OR CAUSE THE SYSTEM TO MALFUNCTION.  PROCEED ONLY IF YOU ARE   ="
    echo "=  AN AUTHORIZED SERVICE PROVIDER AND KNOW WHAT YOU ARE DOING!!!          ="
    echo "================================= WARNING ================================="
    echo
    echo "Please confirm you wish to continue - Type YES if this is correct:"
    read ANSWER
    if [[ "$ANSWER" != "YES" ]]; then
        echo
        echo "Exiting."
        exit 1
    fi
fi

# Find the S10 or S30 enclosures SCSI generic names
GetEnclosureSCSIGenericNames

# If this is an S10, make sure we only have a single server running
if [[ $SYSTEMTYPE == S10 ]]; then
    EnsureS10SingleServer
fi

# Collect the output of the VPDs on each enclosure found
FILESET=
echo
for ENCLOSURE in $ENCLOSURES; do
    echo "Collecting Enclosure $ENCLOSURE VPD-1 Information..."
    ReadFromVPDEEPROM $ENCLOSURE 1 dev-${ENCLOSURE#/dev/}-VPD1-1.txt
    echo "Collecting Enclosure $ENCLOSURE VPD-2 Information..."
    ReadFromVPDEEPROM $ENCLOSURE 2 dev-${ENCLOSURE#/dev/}-VPD2-1.txt
    FILESET="$FILESET dev-${ENCLOSURE#/dev/}-VPD1-1.txt dev-${ENCLOSURE#/dev/}-VPD2-1.txt"
done

# Perform corruption checking on VPD files
echo
echo "Performing checks on enclosure VPDs..."
CheckVPDFiles $FILESET

# If there are no VPD errors and in interactive mode, allow reset of expanders and exit
if [[ "$RESULT" == "" ]]; then
    echo "No VPD issues found"
    if [ "$FIXISSUES" == TRUE -a "$RESETEXPANDERS" == TRUE ]; then
        InteractiveExpanderReset $ENCLOSURE
    fi
    exit 0
fi

# Stop if we are supposed to check, but not correct the VPD issues (default behavior)
if [[ "$FIXISSUES" != TRUE ]]; then
    echo "VPD errors found in files: $RESULT"
    exit 1
fi
FILESET=$RESULT

# Collect template fields to be replaced
TEMPLATESERIALNUMBER=$(grep '</SerialNumber>' $TEMPLATE | head -1 | awk '{print $6}')
TEMPLATESERIALNUMBER=${TEMPLATESERIALNUMBER#*\>}
TEMPLATESERIALNUMBER=${TEMPLATESERIALNUMBER%\<*}
TEMPLATESASADDRESS=$(grep '</EnclosureBaseSasAddress>' $TEMPLATE | head -1 | awk '{print $6}')
TEMPLATESASADDRESS=${TEMPLATESASADDRESS#*\>}
TEMPLATESASADDRESS=${TEMPLATESASADDRESS%\<*}
TEMPLATEMACADDRESS=$(grep '</MacAddress>' $TEMPLATE | head -1 | awk '{print $6}')
TEMPLATEMACADDRESS=${TEMPLATEMACADDRESS#*\>}
TEMPLATEMACADDRESS=${TEMPLATEMACADDRESS%\<*}

EXITCODE=0
# If interactive fix of VPD is enabled
if [[ "$AUTOMATIC" != TRUE ]]; then
    # User selection of enclosure
    COUNT=$(echo $ENCLOSURES | wc -w)
    if (( COUNT > 1 )); then
        UserSelection Enclosure FALSE 0 NONE $ENCLOSURES
        ENCLOSURE=$RESULT
    else
        ENCLOSURE=$ENCLOSURES
    fi
    
    # Allow the user to choose the correct values to use for the three critical items
    UserSelectCriticalFields $ENCLOSURE

    # Create the image file to be written to the VPDs using the critical values
    CreateVPDImageFile

    # Choose the VPDs to be updated and reflash them
    ChooseVPDsAndFlash

    # Recheck VPD copies
    if [ "$WRITEVPD1" == TRUE -o "$WRITEVPD2" == TRUE ]; then
        RecheckEnclosureVPDCopies $ENCLOSURE
        if [[ "$RESULT" != "" ]]; then
            echo "ERROR: Recheck of enclosure $ENCLOSURE VPD copies failed"
            EXITCODE=3
        else
            echo "VPD Copies Match"
            if [[ "$RESETEXPANDERS" == TRUE ]]; then
                InteractiveExpanderReset $ENCLOSURE
            fi
        fi
    fi
else # if automatic fixing of VPDs is enabled
    for ENCLOSURE in $ENCLOSURES; do
        UPDATE=$(echo $FILESET | grep "\-${ENCLOSURE#/dev/}\-")
        if [[ "$UPDATE" != "" ]]; then
            echo
            echo "Attempting to Correct Enclosure $ENCLOSURE"
            # Pull the fields to be saved from both VPDs
            CollectCriticalFields dev-${ENCLOSURE#/dev/}-VPD1-1.txt dev-${ENCLOSURE#/dev/}-VPD2-1.txt
            # Perform integrity checks on the values
            FOUNDERROR=
            if [[ $SYSTEMTYPE == S10 ]]; then
                SelectProperVPDValue $SERIALNUMBER1 $SERIALNUMBER2 16 ALPHANUMCAP $DEFAULTS10SERIAL
            else
                SelectProperVPDValue $SERIALNUMBER1 $SERIALNUMBER2 16 ALPHANUMCAP $DEFAULTS30SERIAL
            fi
            if [[ "$RESULT" == "" ]]; then
                echo "    ERROR: SerialNumber integrity checks failed: VPD1:$SERIALNUMBER1 VPD2:$SERIALNUMBER2"
                FOUNDERROR=TRUE
            else
                SERIALNUMBER=$RESULT
            fi
            if [[ $SYSTEMTYPE == S10 ]]; then
                SelectProperVPDValue $BASESASADDRESS1 $BASESASADDRESS2 16 HEXCAPNZNF $DEFAULTS10BASESAS
            else
                SelectProperVPDValue $BASESASADDRESS1 $BASESASADDRESS2 16 HEXCAPNZNF $DEFAULTS30BASESAS
            fi
            if [[ "$RESULT" == "" ]]; then
                echo "    ERROR: EnclosureBaseSasAddress integrity checks failed: VPD1:$BASESASADDRESS1 VPD2:$BASESASADDRESS2"
                FOUNDERROR=TRUE
            else
                BASESASADDRESS=$RESULT
            fi
            if [[ $SYSTEMTYPE == S10 ]]; then
                SelectProperVPDValue $MACADDRESS1 $MACADDRESS2 12 HEXCAPNZNF $DEFAULTS10MACADDRA $DEFAULTS10MACADDRB
            else
                SelectProperVPDValue $MACADDRESS1 $MACADDRESS2 12 HEXCAPNZNF $DEFAULTS30MACADDRA $DEFAULTS30MACADDRB
            fi
            if [[ "$RESULT" == "" ]]; then
                echo "    ERROR: MacAddress integrity checks failed: VPD1:$MACADDRESS1 VPD2:$MACADDRESS2"
                FOUNDERROR=TRUE
            else
                MACADDRESS=$RESULT
            fi
            if [[ "$FOUNDERROR" == TRUE ]]; then
                echo
                echo   "=============================== WARNING ==============================="
                printf "=    CANNOT AUTOMATICALLY CORRECT THE VPD FOR ENCLOSURE %10s          =\n" $ENCLOSURE
                echo   "=    PLEASE CONTACT YOUR HCP SUPPORT CENTER FOR ASSISTANCE!!!         ="
                echo   "=============================== WARNING ==============================="
                echo
                EXITCODE=3
            else
                # Create the image file to be written to the VPDs using the critical values
                CreateVPDImageFile
                echo "Rewriting enclosure $ENCLOSURE VPD-1"
                perl xeeprom.pl -device=$ENCLOSURE -id=1 -replace $IMAGE
                echo "Rewriting enclosure $ENCLOSURE VPD-2"
                perl xeeprom.pl -device=$ENCLOSURE -id=2 -replace $IMAGE
                RecheckEnclosureVPDCopies $ENCLOSURE
                if [[ "$RESULT" != "" ]]; then
                    echo "ERROR: Recheck of enclosure $ENCLOSURE VPD copies failed"
                    EXITCODE=3
                else
                    echo "VPD Copies Match"
                    if [[ "$RESETEXPANDERS" == TRUE ]]; then
                        sg_senddiag $ENCLOSURE --pf --raw=04,00,00,01,43 # Reset both IOMs
                    fi
                fi
            fi
        else
            echo
            echo "No Updates Required for Enclosure $ENCLOSURE"
        fi
    done
    rm -f $IMAGE 2>/dev/null
fi
exit $EXITCODE
