#!/bin/bash
# /usr/sbin/dm24-upgrade
# 
#  Copyright: 2010-2016, Guralp Systems Ltd.
#  Author:   R.J.Dunlop    <rdunlop@guralp.com>
#  License: GPLv3
#
# Recover a digitiser, detecting the baudrate, issuing trashfram commands if
# required, performing basic config, installing latest firmware etc.

export PATH="/sbin:/usr/sbin:/bin:/usr/bin"

CFGDIR="/etc/conf.d/serial"
FWDIR="/usr/share/firmware"
TLOGDIR="/var/log/trashfram"

cgi_mode=""
exit_code="0"

usage() {
    [ -n "$1" ] && exit_code="$1"

    cat <<EOF

usage: `basename $0` [options...] Port

options:
    --trashfram     Perform a hard factory reset. All parameters will be lost.
    --ids sys ser   New IDs to set following a trashfram (will attempt to
                    preserve old values if not specified).
    --samp # # # #  New sample rates to set.
    --cont # # # #  Continuous tap settings.
    --trig # # # #  Triggered tap settings.
    --gps-baud #    GPS port baudrate to set.
    --in-baud #     Data in port baudrate to set.
    --upgrade       Upgrade files only (this is the default).
    --downgrade     Allow files to downgrade as well as upgrade.
    --force         Files are to be loaded even if they appear identical, or
                    the installed version number cannot be decoded.
    --boot file     Source file for DM24mk3 bootloader.
    --firm file     Source file for DM24mk3 or CD24 firmware.
    --dsp file      Source file for DM24mk3 DSP code.
    --auto-baud     Scan for digitiser baud rate and adjust configuration.
    --verbose       Show most of the digitiser dialog.
    --debug         Output some additional expect script debug information.

Port may be the user designation "Port A" with or without spaces, or the
device name.

The --samp option sets new sample rates and requires four values as supplied
for the SAMPLES/SEC command described in the DM24 manual.

Similarly --cont and --trig select the continuous and triggered tap outputs and
require four values as documented for the SET-TAPS command.
EOF
    exit ${exit_code}
}


warn_baudrate_configuration() {
    # Sending to stderr causes the web interface to display this in red
    cat <<EOF 1>&2

    Caution: The digitiser is running at too high a baud rate for normal
    firmware upgrade.  A temporary lower baud rate will be configured and
    the higher setting restored at the end of the upgrade.

    Unexpected termination of the upgrade process may leave the system
    configured at the lower rate. (Either 115200 or 38400 baud.)

EOF
}


# Functions to check and find firmware files

check_file() {
    local code="$1"
    local username="$2"
    local src="$3"
    local filename="$4"
    local vers
    local min
    local max
    local size

    # A command line firmware file may also set the dm32_type
    if [ "${code}" = "fw" -a "${src}" == "cmdline" ]
    then
        case `basename "${filename}"` in
        dm32_1?_*)
            dm32_type=dm32_1a
            ;;
        dm32_4A_*)
            dm32_type=dm32_4A
            ;;
        dm32_4R_*)
            dm32_type=dm32_4R
            ;;
        dm32_4mA_*)
            dm32_type=dm32_4mA
            ;;
        dm32_4mR_*)
            dm32_type=dm32_4mR
            ;;
        dm32_8mA_*)
            dm32_type=dm32_8mA
            ;;
        dm32_8mR_*)
            dm32_type=dm32_8mR
            ;;
        dm24*)
            dm32_type=dm24
            ;;
        esac
    fi

    if [ -z "${filename}" ]
    then
        echo "No ${username} file specified."
        usage 1
    fi
    if [ -r "${filename}" -a -s "${filename}" ]
    then
        eval ${code}_file="${filename}"
        eval ${code}_file_src="${src}"
    else
        echo "${username} file does not exist or is unreadable/zero length."
        usage 1
    fi

    # Now reduce the filename to a single version number and an optional
    # sub_build for DM24 firmware.
    vers="`basename "${filename}" | sed -e 's/.*[-_]//' -e 's/\..*//'`"
    if [ "${code}" = "fw" ]
    then
        sb="`echo "${vers: -1:1}" | tr A-Z a-z`"
        vers="`echo "${vers}" | sed -e 's/[^0-9]*//g'`"
        case "${sb}" in
        [a-z])
            fw_file_full_vers="${vers}${sb}"
            ;;
        *)
            fw_file_full_vers="${vers}"
            ;;
        esac
    else
        vers="`echo ${vers} | sed -e 's/[^0-9]*//g'`"
    fi
    if [ -z "${vers}" ]
    then
        echo "Cannot determine version number from ${filename}"
        exit 1
    fi

    # Pad the bootloader version adding a leading 0 to the build #
    if [ "${code}" = "boot" -a "${#vers}" -lt 5 ]
    then
        vers="${vers:0:2}0${vers: -2}"
    fi
    eval ${code}_file_vers="${vers}"
}

find_fw() {
    local code="$1"
    local username="$2"
    local pat="$3"
    local sort_args="$4"
    local src

    eval src=\$${code}_file_src
    if [ -z "${src}" ]
    then
        src="`ls "${FWDIR}/"${pat} 2> /dev/null | sort $sort_args | tail -n 1`"
        if [ -r "${src}" ]
        then
            check_file "${code}" "${username}" "default" "${src}"
        fi
    fi
}

check_id() {
    local username="$1"
    local vname="$2"
    local id="$3"

    case "${id}" in
    [0-9A-Z])
        ;;
    [1-9A-Z][0-9A-Z])
        ;;
    [1-9A-Z][0-9A-Z][0-9A-Z])
        ;;
    [1-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z])
        ;;
    [1-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z])
        ;;
    [1-9A-X][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z])
        ;;
    *)
        echo "Bad ${username} ${id}"
        exit 1
        ;;
    esac

    eval $vname="${id}"
}

check_ids() {
    check_id "System ID"     cmdline_sid "$1"
    check_id "Serial number" cmdline_ser "$2"
    set_ids="1"
}

do4() {
    local name="$1"

    eval ${name}_0="$2"
    eval ${name}_1="$3"
    eval ${name}_2="$4"
    eval ${name}_3="$5"
    eval set_$name="1"
}


# Evaluate the parameters

do_trashfram="0"
do_upgrade="0"
allow_downgrade="0"
force_upgrade="0"
some_on_cmdline="0"
cmdline_sid="0"
cmdline_ser="0"
set_ids="0"
set_samp="0"
set_cont="0"
set_trig="0"
auto_baud="0"
verbose="0"
debug="0"

gps_baud="0"
in_baud="0"

boot_file_src=""
fw_file_src=""
dsp_file_src=""
dm32_1a_file_src=""
dm32_4A_file_src=""
dm32_4R_file_src=""
dm32_4mA_file_src=""
dm32_4mR_file_src=""
dm32_8mA_file_src=""
dm32_8mR_file_src=""
dm32_1a_file_vers="0"
dm32_4A_file_vers="0"
dm32_4R_file_vers="0"
dm32_4mA_file_vers="0"
dm32_4mR_file_vers="0"
dm32_8mA_file_vers="0"
dm32_8mR_file_vers="0"

digi_type="unknown"
dm32_type=""


while true
do
    case "$1" in
    -h|--help)
        usage 0
        ;;

    --trashfram)
        do_trashfram=1
        ;;

    --ids)
        check_ids "$2" "$3"
        shift
        shift
        ;;

    --samp)
        do4 "samp" "$2" "$3" "$4" "$5"
        shift
        shift
        shift
        shift
        ;;

    --cont)
        do4 "cont" "$2" "$3" "$4" "$5"
        shift
        shift
        shift
        shift
        ;;

    --trig)
        do4 "trig" "$2" "$3" "$4" "$5"
        shift
        shift
        shift
        shift
        ;;

    --gps-baud)
        gps_baud="$2"
        shift
        ;;

    --in-baud)
        in_baud="$2"
        shift
        ;;

    --upgrade)
        do_upgrade="1"
        ;;

    --downgrade)
        allow_downgrade="1"
        ;;

    --force)
        force_upgrade="1"
        ;;

    --verbose)
        verbose="1"
        ;;

    --debug)
        verbose="1"
        debug="1"
        ;;

    --auto-baud)
        auto_baud="1"
        ;;

    --boot)
        check_file "boot" "Bootloader" "cmdline" "$2"
        shift
        ;;

    --firm)
        check_file "fw" "Firmware" "cmdline" "$2"
        shift
        ;;

    --dsp)
        check_file "dsp" "DSP" "cmdline" $2
        shift
        ;;

    --cgi)
        cgi_mode="--cgi"
        shift
        ;;

    -*)
        echo "Unknown option $1"
        usage 1
        ;;

    *)
        break
        ;;
    esac
    shift
done

port_name="$1"
if [ -z "${port_name}" ]
then
    echo "No port name specified."
    usage 1
fi
if [ -n "$2" ]
then
    echo "Extra parameters after port name"
    usage 1
fi

if [ -n "${boot_file_src}" -o -n "${fw_file_src}" -o -n "${dsp_file_src}" ]
then
    some_on_cmdline="1"
fi

if [ "${some_on_cmdline}" -ne 0 -o "${allow_downgrade}" -ne 0 \
    -o \( "${do_trashfram}" -eq 0 -a "${auto_baud}" -eq 0 \) ]
then
    do_upgrade="1"
fi

if [ "${do_upgrade}" -ne 0 -a "${do_trashfram}" -ne 0 -a "${force_upgrade}" -eq 0 ]
then
    echo "Firmware changes at the same time as a trashfram reset is not"
    echo "recommended, and will probably fail. Better to trashfram first,"
    echo "then modify firmware."
    echo "Use --force if you really need the two steps combined."
    exit 1
fi


# Port name can be in several forms, work out which and transform into the name
# of the configuration file.
# Strip spaces and collapse "Port A" to "PortA"
conf="${port_name/ /}"
[ -c "/dev/${conf}" ] && conf="/dev/${conf}"
if [ -c "${conf}" ]
then
    # Pointer/name in /dev work backwards to config file
    basedev="`basename "${conf}"`"
    conf="`grep ".*device.*=.*${basedev}" "${CFGDIR}"/*.cf /dev/null \
        | sed -n -e '1s/.*\/\([^\/]*\)\.cf:.*/\1/p'`"
else
    # Hopefully a name that collapses to the config file name
    # Flatten to lowercase and then convert back to CamelCase
    # ???
    conf=$(echo $conf | tr 'A-Z' 'a-z' | tr 'a-gp' 'A-GP')
    [ $conf == DAtAout -o $conf == PortDo ] && conf=PortDO
    conf=/etc/conf.d/serial/${conf}.cf
fi

if [ ! -r "${conf}" ]
then
    echo "Cannot find configuration file for ${port_name}"
    usage 1
fi

# Read in the port configuration
cf_name="`gcs_rwvar "gcs_get_varcf" "name" "${conf}"`"
cf_device="`gcs_rwvar "gcs_get_varcf" "device" "${conf}"`"
cf_baud="`gcs_rwvar "gcs_get_varcf" "baud" "${conf}"`"
if [ ! -c "${cf_device}" ]
then
    echo "Configuration file ${conf} does not contain a device setting."
    exit 1
fi

# Warn about potential configuration baud rate change 
[ "${cf_baud}" -ge 230400 ] && warn_baudrate_configuration


# Try to fill in missing firmware files from the standard directories

find_fw boot     "Bootloader" 'CMG-DM24mk3/dm24mk3-boot*.img'	""
find_fw fw       "Firmware"   'CMG-DM24mk3/dm24mk3-[0-9]*.img'	""
find_fw dsp      "DSP"        'CMG-DM24mk3/dm24mk3-dsp*.bin'	""
find_fw dm32_1a  "Firmware"   'CMG-CD24/dm32_1?_*.hex'		"-t _ -k 3"
find_fw dm32_4A  "Firmware"   'CMG-CD24/dm32_4A_*.hex'		"-t _ -k 3"
find_fw dm32_4R  "Firmware"   'CMG-CD24/dm32_4R_*.hex'		"-t _ -k 3"
find_fw dm32_4mA "Firmware"   'CMG-CD24/dm32_4mA_*.hex'		"-t _ -k 3"
find_fw dm32_4mR "Firmware"   'CMG-CD24/dm32_4mR_*.hex'		"-t _ -k 3"
find_fw dm32_8mA "Firmware"   'CMG-CD24/dm32_8mA_*.hex'		"-t _ -k 3"
find_fw dm32_8mR "Firmware"   'CMG-CD24/dm32_8mR_*.hex'		"-t _ -k 3"

if [ "${debug}" -ne 0 ]
then
    echo "boot     ${boot_file_src} ${boot_file_vers}  ${boot_file}"
    echo "fw       ${fw_file_src} ${fw_file_vers}  ${fw_file}"
    echo "dsp      ${dsp_file_src}  ${dsp_file_vers}  ${dsp_file}"
    echo "dm32_1a  ${dm32_1a_file_src}   ${dm32_1a_file_vers}  ${dm32_1a_file}"
    echo "dm32_4A  ${dm32_4A_file_src}   ${dm32_4A_file_vers}  ${dm32_4A_file}"
    echo "dm32_4R  ${dm32_4R_file_src}   ${dm32_4R_file_vers}  ${dm32_4R_file}"
    echo "dm32_4mA ${dm32_4mA_file_src}   ${dm32_4mA_file_vers}  ${dm32_4mA_file}"
    echo "dm32_4mR ${dm32_4mR_file_src}   ${dm32_4mR_file_vers}  ${dm32_4mR_file}"
    echo "dm32_8mA ${dm32_8mA_file_src}   ${dm32_8mA_file_vers}  ${dm32_8mA_file}"
    echo "dm32_8mR ${dm32_8mR_file_src}   ${dm32_8mR_file_vers}  ${dm32_8mR_file}"
fi


# Provide a short name and upload date for the DSP code
if [ -n "${dsp_file_src}" ]
then
    if [ "`date "+%Y"`" -gt "2010" ]
    then
        dsp_info="`basename "${dsp_file}" | sed -e 's/.*-//'` `date "+loaded %Y-%m-%d"`"
    else
        # Ignore system clock in the last century. (RTC battery gone or
        # DCM without NTP).
        dsp_info="`basename "${dsp_file}" | sed -e 's/.*-//'`"
    fi
fi



# Map the config file name to matching service name
service_name="`basename "${conf}" ".cf"`"

if [ "${do_trashfram}" -ne 0 ]
then
    mkdir -p "${TLOGDIR}"
    if [ -w "${TLOGDIR}" ]
    then
        tlogfile="${TLOGDIR}/trashfram-${port_name/ /}-`date +%Y%m%d-%H%M%S`.log"
    else
        tlogfile="`mktemp -t trashfram_log.XXXXXX`"
    fi

    echo "`basename $0` TRASHFRAM log for ${port_name}" > "${tlogfile}"
    date >> "${tlogfile}"
    echo >> "${tlogfile}"
fi


# Stop any existing service
service_stopped=""
if svc "${service_name}" "status" > /dev/null
then
    echo "Suspending service ${service_name}"
    if svc "${service_name}" "stop"
    then
        : Okay
    else
        echo
        echo "Continuing anyway"
        sleep 2
        exit_code=2
    fi
    service_stopped="${service_name}"
fi

# Run the helper script, which is responsible for actually connecting to the
# terminal and carrying out the options we've just validated.
expect -f /usr/lib/dm24-support/dm24-upgrade.exp \
    "${verbose}" \
    "${debug}" \
    "${do_upgrade}" \
    "${do_trashfram}" \
    "${cf_baud}" \
    "${service_name}" \
    "${force_upgrade}" \
    "${cf_device}" \
    "${auto_baud}" \
    "${dm32_1a_file}" \
    "${dm32_1a_file_vers}" \
    "${dm32_8mA_file}" \
    "${dm32_8mA_file_vers}" \
    "${dm32_4mA_file}" \
    "${dm32_4mA_file_vers}" \
    "${dm32_4A_file}" \
    "${dm32_4A_file_vers}" \
    "${dm32_8mR_file}" \
    "${dm32_8mR_file_vers}" \
    "${dm32_4mR_file}" \
    "${dm32_4mR_file_vers}" \
    "${dm32_4R_file}" \
    "${dm32_4R_file_vers}" \
    "${dm32_type}" \
    "${fw_file}" \
    "${fw_file_src}" \
    "${fw_file_vers}" \
    "${fw_file_full_vers}" \
    "${dsp_file}" \
    "${dsp_file_src}" \
    "${dsp_file_vers}" \
    "${boot_file}" \
    "${boot_file_src}" \
    "${boot_file_vers}" \
    "${tlogfile}" \
    "${set_ids}" \
    "${gps_baud}" \
    "${some_on_cmdline}" \
    "${allow_downgrade}" \
    "${cgi_mode}" \
    "${dsp_info}" \
    "${set_ids}" \
    "${in_baud}" \
    "${set_samp}" \
    "${set_cont}" \
    "${set_trig}" \
    "${cmdline_sid}" \
    "${cmdline_ser}" \
    "${samp_0}" \
    "${samp_1}" \
    "${samp_2}" \
    "${samp_3}" \
    "${cont_0}" \
    "${cont_1}" \
    "${cont_2}" \
    "${cont_3}" \
    "${trig_0}" \
    "${trig_1}" \
    "${trig_2}" \
    "${trig_3}"

rc="$?"
[ "${rc}" -ne 0 ] && exit_code="${rc}"


# Resume the original port service
if [ -n "${service_stopped}" ]
then
    echo
    echo "Restarting service ${service_stopped}"
    svc "${service_stopped}" "start"
    rc="$?"
    [ "${rc}" -ne 0 ] && exit_code="${rc}"
fi

if [ "${do_trashfram}" -ne 0 ]
then
    echo "Trashfram log is in ${tlogfile}"
fi

if [ "${exit_code}" -ne 0 ]
then
    echo >&2
    echo "    Program logged at least one failure" >&2
    echo >&2
fi

exit ${exit_code}

# vim: ts=4:sw=4:expandtab
