#!/bin/bash
set -e

# If found, and --no-beta wasn't explicitly passed, use beta mode.  Set by e.g. foxnet.
BIOS_BETA_FILE=/run/jupiter-bios-use-beta

# BIOS versions that can be updated and to what
# 'default'      - specifies what BIOS this version should be updated to by default
# 'samsung'      - specifies what BIOS samsung-memory units should update to
# 'beta'         - if set, the version to update to with --beta or BIOS_BETA_FILE set
# 'samsung_beta' - if set, the version to update to with --beta or BIOS_BETA_FILE set for samsung units
# 'args'         - additional args this update should pass to the updater (not used currently)
#
# - BIOS 19+ are EV2 bios, and can carry forward to the newest.
# - EV1 units no longer supported, BIOS before F7A0006 requires special steps
BIOS_VERSION_MAP='{
                    "F7A0019": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0020": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0021": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0022": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0023": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0024": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0025": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0026": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0027": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0028": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0029": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0030": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0031": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0032": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" },
                    "F7A0100": { "default": "F7A0101", "samsung": "F7A0101", "beta": null, "samsung_beta": null, "args": "" }
                  }'

# Bios to attempt to install
BIOS_FLASHER="/usr/share/jupiter_bios_updater/h2offt"
# Where the bioses (bi-ii?) live
BIOS_DIR="/usr/share/jupiter_bios"
# What to append to the bios name to get the file name
BIOS_SUFFIX="_sign.fd"
# Inhibit file. Do nothing if this exists.
BIOS_INHIBIT_FILE="/foxnet/bios/INHIBIT"

# Util
info() { echo >&2 "$*"; }
err()  { info "!! $*"; }
die() {
  local errstr
  if [[ $# -gt 0 ]]; then
    err "$*"
    errstr="Error: \"$*\""
  fi
  finish 1 false "${errstr:-unknown failure}"
}

finish() {
  local exitcode="$1"
  local changed="$2"
  local comment="${*:3}"
  [[ -z $comment ]] || info "$comment"
  if [[ -n $SALT_STATE ]]; then
    echo
    echo "changed=$changed comment='${comment//\'/}'"
  fi
  exit "$exitcode"
}

##
## Args
##

checkmode=
beta=
# If --no-beta was passed, we don't want to auto-detect beta from /run/jupiter-bios-use-beta
explicit_no_beta=

usage() {
  echo >&2 "!! Usage: $0 [--beta] [check]";
  exit 1
}

while [[ ${#@} -gt 0 ]]; do
  arg="$1"
  if [[ $arg = "check" && -z $checkmode ]]; then
    checkmode=1
  elif [[ $arg = "--no-beta" ]]; then
    beta=
    explicit_no_beta=1
  elif [[ $arg = "--beta" ]]; then
    beta=1
    explicit_no_beta=
  else
    usage
  fi
  shift
done

# Check mode and for BIOS_BETA_FILE
actionstring="BIOS updates"
if [[ -z $explicit_no_beta && -z $beta && -e $BIOS_BETA_FILE ]]; then
  beta=1
  actionstring="BETA BIOS updates due to $BIOS_BETA_FILE"
elif [[ -n $beta ]]; then
  actionstring="BETA BIOS updates"
fi

##
## Main
##

# Die early if inhibited
[[ ! -f $BIOS_INHIBIT_FILE ]] || die "Bios updates inhibited, no action ($BIOS_INHIBIT_FILE)"

# Print action
modestring="Performing"
[[ -z $checkmode ]] || modestring="Checking for"
info "$modestring $actionstring"

# Check for samsung memory
memory_sns=($(dmidecode | awk '/^[^\t]/ {i=0}; /^Memory Device/ {i=1}; /\tPart Number:/ && i { print $3 }' | uniq))
[[ ${#memory_sns[@]} -eq 1 ]] || die "Couldn't identify this unit from DMI, unexpected memory serials: ${memory_sns[*]}"

# Samsung memory modules have S/Ns beginning with K3L
samsung=""
[[ ${memory_sns[0]:0:3} != K3L ]] || samsung=1

# Read & check curent version
currentversion="$(cat /sys/devices/virtual/dmi/id/bios_version)"
echo >&2 "BIOS version: $currentversion"
[[ -n $currentversion ]] || die "Failed to parse bios version from dmidecode"

# Helper to query the bios version map for currentversion+field (with needlessly wordy variable escaping since current
# version is an arbitrary string that has contained bad characters historically)
configfield() {
  local currentversion=$1
  local field=$2
  jq -r --arg currentversion "$currentversion" --arg field "$field" \
     '.[$currentversion] | .[$field] | select(type == "string")' <<< "$BIOS_VERSION_MAP"
}

# Check config table for desired version
if [[ -n $samsung ]]; then
  desiredversion=$(configfield "$currentversion" "samsung")
  desiredversion_beta=$(configfield "$currentversion" "samsung_beta")
else
  desiredversion=$(configfield "$currentversion" "default")
  desiredversion_beta=$(configfield "$currentversion" "beta")
fi

# If beta is set and the beta value for this bios is not null, use it.
[[ -z $beta || -z $desiredversion_beta ]] || desiredversion=$desiredversion_beta

# Args aren't differentiated by samsung/beta/etc currently
desiredargs=$(configfield "$currentversion" "args")

echo >&2 "Desired version: ${desiredversion:-<none>}"

# Up to date if none available or already matching
if [[ -z $desiredversion || $desiredversion = $currentversion ]]; then
  finish 0 no 'No updates configured for this bios'
fi

# No action if we're up-to-date
if [[ $currentversion = "$desiredversion" ]]; then
  finish 0 no "Installed bios $currentversion is up-to-date"
fi

# Only attempt if on battery.  A bug in some BIOS versions results in 'Unknown' here, but that can only be reached in
# the charging state, sooo.
#
# If the unit has no BAT1, just skip the check -- the actual BIOS update tool will reject them, we'll just prompt
# needlessly, but this is better than leaving dev units out silently.
ac="$(cat /sys/class/power_supply/BAT1/status || echo "Unknown")"
[[ $ac = "Charging" || $ac = "Full" || $ac = "Unknown" ]] || finish 2 no "Cannot attempt bios update unless AC is connected"

# Make sure we have the files to install this version
biosfile="$BIOS_DIR/$desiredversion$BIOS_SUFFIX"
[[ -x $BIOS_FLASHER ]] || die "Could not find or execute flasher at \"$BIOS_FLASHER\""
[[ -f "$biosfile" ]] || die "Could not find expected bios at: $biosfile"
#
# Update needed
#
[[ -z $checkmode ]] || finish 7 yes "Updates available, not applying in check mode"

# Try to flash
#   Flasher is cwd-sensitive
cd "$(dirname "$BIOS_FLASHER")"
# shellcheck disable=SC2206 # desiredargs expanded on purpose
cmd=("$BIOS_FLASHER" "$biosfile" -all $desiredargs)
info "Running: " "${cmd[@]@Q}"
if "${cmd[@]}"; then
  finish 0 yes "Applied bios update to version $desiredversion"
fi

die "Attempted to flash bios but failed"
