#!/bin/sh
################################################################################
#
# CMe3100 update script.
#
################################################################################

KERNEL_MNT_DIR="/mnt/new_kernel"
MACHINE_INFO="/application/CMe3100/internal/machine.info"
PROGRESS_FILE="/application/progress.tmp"
SQL_LOG_DB="/application/CMe3100/appdata/currentstorage/log_db_cme3100.sqlite"
UPDATE_SW_DIR="/application/updatesw"
UPDATE_SW_APPL_DIR="$UPDATE_SW_DIR/CMe3100"

# ------------------------------------------------------------------------------
# Log to console and insert message in log database.
#
# $1 : message to log
# ------------------------------------------------------------------------------
log() {
  echo "$1"

  NOW=$(date +%s000)
  sqlite3 $SQL_LOG_DB "INSERT INTO Log (created, severity, message, source, deviceId) VALUES ('$NOW', -1, '$1', 'System: update', -1);"
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Invalidates an update.
# 1) Log message to database
# 2) Cleanup files and folders created during update.
#
# $1 : message to store in database.
# ------------------------------------------------------------------------------
invalidUpdate() {
  log "Update invalid. $1. State was $state."

  # Cleanup files and folders.
  rm -rf $UPDATE_SW_DIR
  rm -f /application/progress.tmp

  # Exit update script.
  exit 1
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Current version is to old to be updated to the new version
# ------------------------------------------------------------------------------
invalidVersion() {
  invalidUpdate "Version $RAW_REQ_VERSION has to be installed before this update."
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Check if there is a progress file. This file is used to keep track of where we
# are in the update process.
#
# If the file files exists it means an update is in progress and this method logs
# and returns.
#
# If there is no progress file, there could be a new update just being started.
# In this case create the progress file with state 0 written in it.
# ------------------------------------------------------------------------------
checkProgressFile() {
  if [ -f $PROGRESS_FILE ]; then
    log "Progress file $PROGRESS_FILE found."
  else
    log "Progress file  $PROGRESS_FILE not found. Creating it."
    touch "$PROGRESS_FILE"
    echo 0 >$PROGRESS_FILE
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Unpack zipped update file.
# ------------------------------------------------------------------------------
unpackZippedUpdateFile() {
  if ls $UPDATE_SW_DIR/*.tar.bz2* &>/dev/null; then
    log "Extracting with bzip2."

    if bzip2 -d $UPDATE_SW_DIR/*.tar.bz2 &>/dev/null; then
      echo 1 >$PROGRESS_FILE
    else
      invalidUpdate "Error extracting zipped update file."
    fi
  elif ls $UPDATE_SW_DIR/*.tar &>/dev/null; then
    echo 1 >$PROGRESS_FILE
  else
    invalidUpdate "Unsupported update file format. Should be .tar or .bz2"
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Unpack tared update file.
# ------------------------------------------------------------------------------
unpackTarFile() {
  if ls $UPDATE_SW_DIR/*.tar &>/dev/null; then
    log "Extracting with tar."

    if tar xvf $UPDATE_SW_DIR/*.tar -C $UPDATE_SW_DIR &>/dev/null; then
      rm -f $UPDATE_SW_DIR/*.tar
      echo 2 >$PROGRESS_FILE
    else
      invalidUpdate "Error extracting tared update file."
    fi
  else
    invalidUpdate "Update file missing in update folder."
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Update the update script (i.e. this script) if updatesw contains one.
# ------------------------------------------------------------------------------
updateUpdateScript() {
  if [ -f $UPDATE_SW_DIR/S90update ]; then
    if diff /etc/init.d/S90update $UPDATE_SW_DIR/S90update >/dev/null; then
      log "S90update script identical. Skipping update."
    elif cp $UPDATE_SW_DIR/S90update /etc/init.d/S90update &>/dev/null; then
      log "Updating S90update script."
      chmod 755 /etc/init.d/S90update
      rm -f $UPDATE_SW_DIR/S90update
      echo 3 >$PROGRESS_FILE
      reboot -f
    else
      invalidUpdate "Failed to update S90update script."
    fi
  fi
  echo 3 >$PROGRESS_FILE
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Count entries in log database
#
# $1 : severity to count. If left out, all entries are counted
# ------------------------------------------------------------------------------
countEntries() {
  if [ -n "$1" ]; then
    result=$(sqlite3 $SQL_LOG_DB "SELECT COUNT(*) FROM Log WHERE Severity='$1';")
  else
    result=$(sqlite3 $SQL_LOG_DB "SELECT COUNT(*) FROM Log;")
  fi

  return "$result"
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Delete entries in log database
#
# $1 : severity of log entry to delete
# $2 : number of items to drop. If left out, all entries for severity are removed
# ------------------------------------------------------------------------------
deleteLogEntriesWithSeverity() {
  if [ -n "$2" ]; then
    sqlite3 $SQL_LOG_DB "DELETE FROM Log
WHERE logId IN (
    SELECT logId
    FROM Log
    WHERE severity = '$1'
    ORDER BY created ASC, logId ASC
    LIMIT '$2'
);"
  else
    sqlite3 $SQL_LOG_DB "DELETE FROM Log WHERE severity = '$1';"
  fi
  sqlite3 $SQL_LOG_DB "VACUUM;"
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Truncate log database to avoid greatly oversized log databases.
# ------------------------------------------------------------------------------
truncateLogDatabaseIfOversized() {
  log_db_size_mb=$(du -m "$SQL_LOG_DB" | awk '{ print $1}')

  if [ "$log_db_size_mb" -gt 100 ]; then
    log "Log database too large ($log_db_size_mb MB), truncated during application start"
    severity=-2
    numberOfCleans=0

    while [ "$log_db_size_mb" -gt 100 ]; do
      case $severity in
      -2)
        deleteLogEntriesWithSeverity $severity
        severity=$((severity + 1))
        ;;
      *)
        deleteLogEntriesWithSeverity $severity 1000
        countEntries $severity
        if [ "$?" -eq 0 ]; then
          severity=$((severity + 1))
        fi
        ;;
      esac

      numberOfCleans=$((numberOfCleans + 1))

      log_db_size_mb=$(du -m "$SQL_LOG_DB" | awk '{ print $1}')

      if [ "$numberOfCleans" -gt 50 ]; then
        break
      fi

      if [ "$severity" -gt 4 ]; then
        break
      fi
    done

    log_db_size_kb=$(du -k "$SQL_LOG_DB" | awk '{ print $1}')
    log "Truncation done (or aborted), size is now $log_db_size_mb MB ($log_db_size_kb kB)"
  else
    echo "Log database size within limits, NOT touching it"
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Validate linux checksum.
# ------------------------------------------------------------------------------
linuxChecksumValid() {
  LINUX_CHECKSUM=$(grep -i 'checksum' $UPDATE_SW_DIR/linux.config | cut -f2 -d'=')
  LINUX_SHA1=$(sha1sum $UPDATE_SW_DIR/rootfs.tar.bz2 | awk '{print $1}')

  [ "$LINUX_CHECKSUM" == "$LINUX_SHA1" ]
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Validate Java application.
# ------------------------------------------------------------------------------
javaApplicationChecksumValid() {
  APP_CHECKSUM=$(grep -i 'checksum' $UPDATE_SW_DIR/java.config | cut -f2 -d'=')
  APP_SHA1=$(sha1sum $UPDATE_SW_DIR/cme3100-*.tar.gz | awk '{print $1}')

  [ "$APP_CHECKSUM" == "$APP_SHA1" ]
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Prepare for mounting new kernel. Including cleaning up any old stuff in
# directory.
# ------------------------------------------------------------------------------
mountNewKernel() {
  rm -rf $KERNEL_MNT_DIR &>/dev/null
  mkdir -p $KERNEL_MNT_DIR

  CURRENT_ROOTFS=$(fw_printenv rootfs | cut -f2 -d'=')
  if [[ "$CURRENT_ROOTFS" == "2" ]]; then
    mount -t auto -v /dev/mmcblk0p3 $KERNEL_MNT_DIR
  elif [[ "$CURRENT_ROOTFS" == "3" ]]; then
    mount -t auto -v /dev/mmcblk0p2 $KERNEL_MNT_DIR
  else
    invalidUpdate 'Failed to determine current rootfs partition'
  fi

  rm -rf ${KERNEL_MNT_DIR:?}/*
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Extract linux kernel and update state to install Java application in next
# stage in the update loop.
# ------------------------------------------------------------------------------
extractRootFileSystem() {
  if linuxChecksumValid; then
    log "Extracting new root filesystem."

    mountNewKernel

    if bzcat $UPDATE_SW_DIR/rootfs.tar.bz2 | tar x -C $KERNEL_MNT_DIR; then
      cp /etc/shadow $KERNEL_MNT_DIR/etc/shadow
      cp /etc/network/interfaces $KERNEL_MNT_DIR/etc/network/interfaces
      cp /etc/hosts $KERNEL_MNT_DIR/etc/hosts
      cp /etc/hostname $KERNEL_MNT_DIR/etc/hostname
    else
      invalidUpdate "Update failed, unable to extract root filesystem."
    fi
  else
    invalidUpdate "Update failed, invalid or corrupted rootfs file."
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Validate the new version if it's allowed to update from current version.
# ------------------------------------------------------------------------------
validateNewJavaVersion() {
  RAW_REQ_VERSION=$(grep -i 'required.version' $UPDATE_SW_DIR/java.config | cut -f2 -d'=')
  CURRENT_VERSION=$(unzip -q -p /application/CMe3100/Elvaco-CMSeries-Application.jar META-INF/MANIFEST.MF | grep "Manifest-Version" | awk '{print $2}')
  CURRENT_VERSION=${CURRENT_VERSION//-/.}
  CURRENT_MAJOR=$(echo "$CURRENT_VERSION" | awk -F "." '{print $1}')
  CURRENT_MINOR=$(echo "$CURRENT_VERSION" | awk -F "." '{print $2}')
  CURRENT_FIX=$(echo "$CURRENT_VERSION" | awk -F "." '{print $3}')

  REQ_VERSION=${RAW_REQ_VERSION//-/.}
  REQ_MAJOR=$(echo "$REQ_VERSION" | awk -F "." '{print $1}')
  REQ_MINOR=$(echo "$REQ_VERSION" | awk -F "." '{print $2}')
  REQ_FIX=$(echo "$REQ_VERSION" | awk -F "." '{print $3}')

  if [ "$CURRENT_MAJOR" -lt "$REQ_MAJOR" ]; then
    invalidVersion
  elif [ "$CURRENT_MAJOR" -eq "$REQ_MAJOR" ] && [ "$CURRENT_MINOR" -lt "$REQ_MINOR" ]; then
    invalidVersion
  elif [ "$CURRENT_MAJOR" -eq "$REQ_MAJOR" ] && [ "$CURRENT_MINOR" -eq "$REQ_MINOR" ] && [ "$CURRENT_FIX" -lt "$REQ_FIX" ]; then
    invalidVersion
  else
    log "Valid update, continuing."
  fi
}

# ------------------------------------------------------------------------------
# Generate new machine.info.
# ------------------------------------------------------------------------------
generateMachineInfo() {
  # Copy machine.config information to new file
  cp -f /application/CMe3100/internal/machine.config $MACHINE_INFO

  # Add empty variables (to be filled in later on)
  # shellcheck disable=SC2129
  echo "product.sw=" >>$MACHINE_INFO
  echo "product.mac=" >>$MACHINE_INFO
  echo "product.kv=" >>$MACHINE_INFO

  # Remove password from file
  sed -i '/product.password/d' $MACHINE_INFO

  # Get MAC address and write it to file
  MAC_ADDR=$(ifconfig eth0 | awk '/HWaddr/ {print $5}')
  sed -i "s/^product.mac=.*/product.mac=$MAC_ADDR/g" $MACHINE_INFO

  # Get kernel version and write it to file
  KERNEL_VERSION=$(uname -a | awk '{print $3}')
  sed -i "s/^product.kv=.*/product.kv=$KERNEL_VERSION/g" $MACHINE_INFO
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Set root fs.
# ------------------------------------------------------------------------------
setRootFs() {
  CURRENT_ROOTFS=$(fw_printenv rootfs | cut -f2 -d'=')
  if [[ "$CURRENT_ROOTFS" == "2" ]]; then
    fw_setenv rootfs 3
  elif [[ "$CURRENT_ROOTFS" == "3" ]]; then
    fw_setenv rootfs 2
  else
    invalidUpdate 'Failed to determine current rootfs partition'
  fi
}
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# $1 : command to execute. Only start is supported.
# ------------------------------------------------------------------------------
case "$1" in
start)

  if [ -d /application/updatesw ]; then
    truncateLogDatabaseIfOversized
    log "New update available!"
    checkProgressFile

    # Check update state.
    state=$(cat $PROGRESS_FILE)
    while true; do

      log "Update is in state $state."
      case "$state" in
      0)
        unpackZippedUpdateFile
        ;;
      1)
        unpackTarFile
        ;;
      2)
        updateUpdateScript
        ;;
      3)
        if [ -f $UPDATE_SW_DIR/java.config ]; then
          dos2unix $UPDATE_SW_DIR/java.config
          if ! javaApplicationChecksumValid; then
            invalidUpdate "Update failed, invalid or corrupted application file."
          fi
          gunzip $UPDATE_SW_DIR/cme3100-*tar.gz
        else
          invalidUpdate "Update failed, missing file."
        fi

        if [ -f $UPDATE_SW_DIR/linux.config ]; then
          extractRootFileSystem
          echo 5 >$PROGRESS_FILE
        else
          log "No rootfs update: patch release."
          echo 7 >$PROGRESS_FILE
        fi
        ;;
        # 4) is no more, it used to be a separate step for extracting the rootfs.tar archive,
        # which is now done in extractRootFileSystem (stage 3)
      5)
        log "Removing compressed linux files."
        if rm -f $KERNEL_MNT_DIR/*.tar && rm -f $KERNEL_MNT_DIR/*.tar.bz2 &>/dev/null; then
          echo 6 >$PROGRESS_FILE
        fi
        ;;
      6)
        setRootFs
        echo 7 >$PROGRESS_FILE
        ;;
      7)
        log "Extracting with tar."
        if tar xvf $UPDATE_SW_DIR/cme3100-*.tar -C $UPDATE_SW_DIR &>/dev/null; then
          echo 8 >$PROGRESS_FILE
        else
          invalidUpdate "Unable to extract application tar file."
        fi
        ;;
      8)
        if [ -d $UPDATE_SW_APPL_DIR/cme3100-rootfs ]; then
          cp -r $UPDATE_SW_APPL_DIR/cme3100-rootfs/* /
        fi
        echo 9 >$PROGRESS_FILE
        ;;
      9)
        rm -rf /application/CMe3100/appdata/defaultpluginconfig
        mv $UPDATE_SW_APPL_DIR/appdata/defaultpluginconfig /application/CMe3100/appdata/defaultpluginconfig
        echo 10 >$PROGRESS_FILE
        ;;
      10)
        if [ -f $UPDATE_SW_APPL_DIR/changescripts/update.sh ]; then
          log "Running update scripts"
          $UPDATE_SW_APPL_DIR/changescripts/update.sh
        fi
        echo 11 >$PROGRESS_FILE
        ;;
      11)
        mkdir -p /application/tmp
        rm -rf /application/tmp/lib &>/dev/null
        mv /application/CMe3100/lib /application/tmp/ &>/dev/null
        if mv $UPDATE_SW_APPL_DIR/lib /application/CMe3100/lib &>/dev/null; then
          log "Lib folder successfully updated"
          echo 12 >$PROGRESS_FILE
        else
          rm -rf /application/tmp/lib &>/dev/null
          mv /application/tmp/lib /application/CMe3100/ &>/dev/null
          invalidUpdate "Unable to copy lib folder."
        fi
        ;;
      12)
        log "Updating main application"
        rm -f /application/CMe3100/Elvaco-CMSeries-Application.jar
        mv $UPDATE_SW_APPL_DIR/Elvaco-CMSeries-Application.jar /application/CMe3100/Elvaco-CMSeries-Application.jar
        SW_VERSION=$(unzip -q -p /application/CMe3100/Elvaco-CMSeries-Application.jar META-INF/MANIFEST.MF | grep "Manifest-Version" | awk '{print $2}')
        if [ ! -f $MACHINE_INFO ]; then
          generateMachineInfo
        fi
        sed -i "s/^product.sw=.*/product.sw=$SW_VERSION/g" $MACHINE_INFO
        echo 13 >$PROGRESS_FILE
        ;;
      13)
        log "Updating web application"
        # Remove JSP.war and extracted war files
        rm -f /application/CMe3100/webapp/*JSP*.war && rm -rf /application/CMe3100/webapp/.web-apps-target/
        if [ -d $UPDATE_SW_APPL_DIR/webapp ]; then
          mv $UPDATE_SW_APPL_DIR/webapp/* /application/CMe3100/webapp/
        fi
        echo 14 >$PROGRESS_FILE
        ;;
      14)
        log "Updating application resources"
        cp -r $UPDATE_SW_APPL_DIR/resource /application/CMe3100/
        rm -rf $UPDATE_SW_APPL_DIR/resource
        echo 15 >$PROGRESS_FILE
        ;;
      15)
        rm -rf /application/CMe3100/appdata/defaultconfig
        mv $UPDATE_SW_APPL_DIR/appdata/defaultconfig /application/CMe3100/appdata/defaultconfig
        cp -f /application/CMe3100/appdata/defaultconfig/common/common.tdf /application/CMe3100/appdata/currentconfig/common/common.tdf
        cp -f /application/CMe3100/appdata/defaultconfig/command/*.tdf /application/CMe3100/appdata/currentconfig/command/
        echo 16 >$PROGRESS_FILE
        ;;
      16)
        rm -rf /application/CMe3100/appdata/defaultstorage
        mv $UPDATE_SW_APPL_DIR/appdata/defaultstorage /application/CMe3100/appdata/defaultstorage
        echo 17 >$PROGRESS_FILE
        ;;
      17)
        if [ -d $UPDATE_SW_APPL_DIR/Linux ]; then
          mkdir -p /application/CMe3100/Linux/
          rm -rf /application/CMe3100/Linux/* &>/dev/null
          cp -r $UPDATE_SW_APPL_DIR/Linux/* /application/CMe3100/Linux/
        fi
        log "Java software updated successfully"
        echo 18 >$PROGRESS_FILE
        ;;
      18)
        log "Checking for updates to setup.exe"
        if [ -f $UPDATE_SW_DIR/setup.exe ]; then
          log "Mounting usbdrive"
          mkdir -p /mnt/usbhdd
          mount -t auto /dev/mmcblk0p6 /mnt/usbhdd
          cp $UPDATE_SW_DIR/setup.exe /mnt/usbhdd/setup.exe
          log "Updated setup.exe"
          log "Unmounting usbdrive"
          umount /dev/mmcblk0p6
        else
          log "No new setup.exe found"
        fi
        break
        ;;
      *)
        break
        ;;
      esac
      state=$(cat $PROGRESS_FILE)
    done

    if [ -n "$CURRENT_ROOTFS" ]; then
      log "Unmounting."
      umount $KERNEL_MNT_DIR
    fi

    if [ -f $UPDATE_SW_DIR/factory_reset ]; then
      log "Factory reset initialized."
      cp $UPDATE_SW_DIR/factory_reset /application/factory_reset.boot
    elif [ -f $UPDATE_SW_DIR/linux.config ]; then
      log "Rebooting after linux update."
    fi

    log "Removing software update folder $UPDATE_SW_DIR"
    rm -rf $UPDATE_SW_DIR &>/dev/null

    log "Removing progress file $PROGRESS_FILE"
    rm -f $PROGRESS_FILE &>/dev/null

    reboot -f
  else
    log "No updates."
  fi
  ;;
*)
  echo "Usage: $0 {start}"
  exit 1
  ;;
esac

exit 0
# ------------------------------------------------------------------------------
