#!/bin/bash
# console-config/src/sbin-scripts/serial-port-setup
#
#  Copyright: ©2014, Güralp Systems Ltd.
#  Author: Kelly Dunlop <kdunlop@guralp.com>
#  License: GPLv3
#
# This script allows the user to configure serial ports. It allows configuring
# a serial port for one of the following things:
#
#        None                           - The port is ignored
#        Terminal                       - Port used for user access
#        PPP out                        - PPP network connection
#        GCF in                         - Inbound GCF data gathering
#        GCF out                        - Outbound GCF data transmission
#        TCP serial converter           - Serial data to TCP link converter
#        Morningstar Modbus             - Modbus interface
#        Modem in                       - Modem answering service
#        Serial recorder                - Record info from serial port
#        Fast CD24 in                   - Low latency data acquisition from CD24
#        RTD out                        - USGS RTD format output
#        KVH DSP1500 Digital Gyro       - Digital gyroscope input
#
# The array that holds the information that is actually used is declared in common.sh.

# Important global variables

DIALOG_RESULTFILE="`mktemp`"
DIALOG_DEFAULT_W="60"

# The default width is not enough for some tables so we need some extra characters.
DIALOG_WIDER_DEFAULT="75"

CONFIGDIR="/etc"
SERIAL_DEVICE_GROUP="instcfg"
SERIAL_DEVICE_MODE="0620"



# The various routines are split into several modules under /usr/lib, and these
# are all sourced into this file. This aids programming and maintenance.
for LIB in /usr/lib/console-config/serial-*.sh
do
    . "${LIB}"
done

. /usr/lib/console-config/common.sh
. /usr/lib/console-config/timing-serial.sh
#. /usr/lib/console-config/timing-common.sh



#
# Top-level configuration of serial ports.
# This handles the top level config file which just contains the device name,
# the baud rate and the function.  The obtaining of the specific function
# parameters is handled by separate files which are name serial-*.sh.
#



# Set permissions and group on a file.
# $1 is the filename
# $2 is the permission
# $3 is the group
cconf_set_perms_and_group() {
    if [ -z "$1" ]
    then
        return
    fi
    if [ -z "$2" ]
    then
        return
    fi
    if [ -z "$3" ]
    then
        return
    fi

    chmod $2 $1 > /dev/null 2>&1
    chgrp $3 $1 > /dev/null 2>&1
}



# Create a directory structure at the given destination with the group value
# and permissions as requested.
#   $1 name of the directory to create - uses -p so parent directories don't
#   need to exist.
#   $2 name of group - set to conf if blank
#   $3 permission as in chmod - set to 775 if blank
cconf_mkdir() {
    local dest=$1
    local group=$2
    local mode=$3

    if [ -d $dest ]
    then
        if [ -n "$group" ]
        then
            chgrp $group $dest > /dev/null 2>&1
        fi
        if [ -n "$mode" ]
        then
            chmod $mode $dest > /dev/null 2>&1
        fi
    else
        mkdir -p -m ${mode:- 775} $dest
        chgrp ${group:conf} $dest > /dev/null 2>&1
    fi
}



# Create the given config file and give it the correct group and permissions.
# This deletes an existing config file and then recreates it with a line containing
# the date it was created.
#   $1 is the name of the file to create.
#   $2 is the group the file should be owned by (may be blank in which
#   case conf is assumed.
#   $3 is the mode to create the file with (may be blank in which case 0664
#   will be set.
cconf_create_config_file() {
    local file="$1"
    local group="${2:-conf}"
    local mode="${3:- 0664}"

    rm -f $file

    cconf_mkdir $(dirname "$file") $group 2775
    echo "# File created by console-config $(date)" > "$file"
    chgrp $group "$file" > /dev/null 2>&1
    chmod $mode "$file" > /dev/null 2>&1
}



# List the gdi sockets available for use.
#   $1 is either source or sink indicating which to return
#   $2 is the current value in the config file for the gdi socket
#   Sets MENU an array for use with dialog to select the values
#   Set the variable GDI_INSTANCE to the value of the current instance which
#   matches the value of the gdi_socket in $2
gdi_list_sockets() {
    local currentsocketpath="$2"
    local gdivalue=""
    local i=0
    local gdicfg socketpath desc instance

    MENU=()

    for gdicfg in "${CONFIGDIR}/gdi-base/"*.local
    do

        socketpath="$(cf2get ${gdicfg} "[]" "${1}_socket_path" )"
        if [ -z "${socketpath}" ]
        then
            continue
        fi

        desc="$(cf2get ${gdicfg} "[]" "application_description")"

        instance="$(basename "${gdicfg}" ".local")"

        # This is the file for the current GDI multiplexor in use
        if [ "${currentsocketpath}" = "${socketpath}" ]
        then
            GDI_INSTANCE=$instance
        fi

        MENU+=("${instance}" "${desc}")

        ((i++))
    done
}



# Return the gdi_socket value for the gdi multiplexor with the given num (note it
# can be a number or "default" so we can't use return.
#    $1 is the num for the GDI multiplexor we want the socket value for.
#    Returns the pathname in the variable GDI_SOCKET_PATH.
gdi_get_socket_path() {
    local fname="${CONFIGDIR}/gdi-base/"${1}.local

    # source_socket_path should be configurable....  TODO
    GDI_SOCKET_PATH="$(cf2get ${fname} "[]" source_socket_path)"
}



# List the gdi2gcf sockets available for use.
#   $1 is the current value in the config file for the gdi2gcf socket
#   Sets MENU an array for use with dialog to select the values
#   Set the variable GDI2GCF_INSTANCE to the value of the current instance which
#   matches the value of the gcfout_dbdir in $1
gdi2gcf_list_sockets() {
    local currentsocketpath="$1"
    local gdi2gcfvalue=""
    local i gdi2gcfcfg socketpath desc instance

    MENU=()

    i=0
    for gdi2gcfcfg in "${CONFIGDIR}/gdi2gcf/"*.local
    do
        socketpath="$(cf2get ${gdi2gcfcfg} "[]" "dbdir" )"
        if [ -z "${socketpath}" ]
        then
            continue
        fi

        desc="$(cf2get ${gdi2gcfcfg} "[]" "application_description")"

        instance="$(basename "${gdi2gcfcfg}" ".local")"

        # This is the file for the current GDI2GCF converter being used.
        if [ "${currentsocketpath}" = "${socketpath}" ]
        then
            GDI2GCF_INSTANCE=$instance
        fi

        MENU+=("${instance}" "${desc}")

        ((i++))
    done
}



# Return the gcfout_dbdir value for the gdi2gcf converter with the given
# num (note it can be a number ot "default".)
#    $1 is the num for the GDI2GCF converter we want the socket value for.
#    Returns the pathname in the variable GDI2GCF_DBDIR
gdi2gcf_get_socket_path() {
    local fname="${CONFIGDIR}/gdi2gcf/"${1}.local

    GDI2GCF_DBDIR="$(cf2get ${fname} "[]" dbdir)"
}



# Check it will be possible to write the file ie it checks the directory exists and is writable
# and executable.   - can't do much more without a C prog
#   $1 - name of file to check
#   Returns 0 if file exists and 1 if not
cconf_check_file() {
    local name="`dirname $1`"

    if [ -d "${name}" -a -w "${name}" -a -x "${name}" ]
    then
        return 0
    else
        return 1
    fi
}



# List serial ports available to be configured
#  Returns the name of the port selected in the file DIALOG_RESULTFILE.
serial_list_ports() {

    serial_build_port_menu
    if [ -z "${MENU}" ]
    then
        clear
        echo "No serial ports available to configure."
        exit 1
    fi

    dialog ${MENU_EXTRA_ARGS} \
        --title "Serial Port Configuration" \
        --menu "Select serial port to configure" \
        0 ${DIALOG_DEFAULT_W} 0 \
        "${MENU[@]}" \
        2> "${DIALOG_RESULTFILE}"
    RESULT=$?

    clear
    if [ "${RESULT}" -ne 0 ]
    then
        echo "No serial port selected."
        exit 1
    fi
}



# List functions available for serial ports
#   $1 current portname
#   $2 current function
serial_list_functions() {

    if [ -z "${FUNC_LIST}" ]
    then
        clear
        echo "No functions available to configure."
        exit 1
    fi

    dialog --default-item "$2" \
        --title "$1 configuration" \
        --menu "Select function for $1" \
        0 ${DIALOG_WIDER_DEFAULT} 0 \
        "${FUNC_LIST[@]}" \
        2> "${DIALOG_RESULTFILE}"
    RESULT=$?

    clear
    if [ "${RESULT}" -ne 0 ]
    then
        echo "No function selected."
        exit 1
    fi
}



# main loop
#  If any section is cancelled we exit.  Is this reasonable ????
while true
do
    PORTNAME=""
    NEW_FUNCTION=""

    # If there is a parameter it should be the name of the port to change
    if [ $# -gt 0 ]
    then
        if [ -n "$1" ]
        then
            PORTNAME="$1"
        fi
    fi
    # This is just so that we catch it if the user calls us with "" as a parameter so
    # the we don't error.
    if [ -z "${PORTNAME}" ]
    then
        # List ports for user to select one
        serial_list_ports

        PORTNAME="`cat "${DIALOG_RESULTFILE}"`"
    fi

    TOPLVL_CFGFILE=${SERIAL_CFDIR}/${PORTNAME}.cf
    CURR_FUNC="`cfget ${TOPLVL_CFGFILE} function`"
    CURR_BAUD="`cfget ${TOPLVL_CFGFILE} baud`"

    TOP_NAME="`cfget ${TOPLVL_CFGFILE} name`"
    if [ -z "${TOP_NAME}" ]
    then
        echo Missing value 'name' from config file ${TOPLVL_CFGFILE}
        exit 1
    fi
    TOP_DEVICE="`cfget ${TOPLVL_CFGFILE} device`"
    if [ -z "${TOP_DEVICE}" ]
    then
        echo Missing value 'device' from config file ${TOPLVL_CFGFILE}
        exit 1
    fi


    if [ "${CURR_FUNC}" = "NMEA out" -o "${CURR_FUNC}" = "NMEA in" -o \
         "${CURR_FUNC}" = "Trigger output" -o "${CURR_FUNC}" = "Timestamp in" ]
    then
        dialog --title "Serial Port Configuration" \
               --msgbox "${PORTNAME} cannot be changed from function ${CURR_FUNC}" \
               0 0 2> "${DIALOG_RESULTFILE}"
        NEW_FUNCTION="${CURR_FUNC}"
    else

        # List possible functions for a serial port for user to select one
        serial_list_functions "${PORTNAME}" "${CURR_FUNC}"

        NEW_FUNCTION="`cat "${DIALOG_RESULTFILE}"`"

        # Special case for new function of None save function then restart and exit
        if [ "${NEW_FUNCTION}" = "None" ]
        then
            cfset ${TOPLVL_CFGFILE} function="${NEW_FUNCTION}"

            # Restart the service for this port
            echo Restarting ${PORTNAME} service
            svc ${PORTNAME} restart
            exit 0
        fi
    fi

    # List baud rates available for user to select
    serial_select_baud "Select baud rate for ${PORTNAME}" "${CURR_BAUD}"
    if [ $? -ne 0 ]
    then
        clear
        echo Cancelled
        exit 1
    fi

    NEW_BAUD="`cat "${DIALOG_RESULTFILE}"`"

    # Handle the function-specific setup. "None" has already been handled above
    # as a special case.
    case "${NEW_FUNCTION}" in
    "NMEA out")
        # Call the function that does the NMEA out setup from the
        # timing-serial.sh file. First parameter is port ID and second is
        # the mode (which we pass as blank to inherit the current port
        # settings).
        # Pass the portname as a parameter but the baud rate is a global variable
        # named SERIAL_BAUD.
        SERIAL_BAUD="${NEW_BAUD}"
        nmea_out_and_svc_restart "${PORTNAME}" ""
        echo Service NMEA out restarted on ${PORTNAME}
        exit
        ;;

    "NMEA in")
        # Call the function that does the NMEA in setup from the timing-serial.sh file.
        # Pass the portname as a parameter but the baud rate is a global variable
        # named SERIAL_BAUD.
        SERIAL_BAUD="${NEW_BAUD}"
        nmea_in_svc_restart ${PORTNAME}
        exit
        ;;

    "Timestamp in")
        # Call the function that does the timestamp receiver/translator setup
        # from the timing-serial.sh file. Pass the portname as a parameter but
        # the baud rate is a global variable named SERIAL_BAUD.
        SERIAL_BAUD="${NEW_BAUD}"
        timestamp_in_and_svc_restart ${PORTNAME}
        exit
        ;;

    "Terminal" | "Modem in" | "Serial recorder" | "Trigger output")
        # Nothing extra to be done for these
        ;;

    "PPP out")
        /usr/lib/ppp-wrapper/ppp-config "/etc/conf.d/serial/ppp2/${PORTNAME}" || exit 1
        ;;

    "GCF in")
        serial_gcfin_parameters "${PORTNAME}"
        ;;

    "GCF out")
        serial_gcfout_parameters "${PORTNAME}" "${TOP_NAME}"
        ;;

    "TCP serial")
        serial_tcpserial_parameters "${PORTNAME}"
        ;;

    "MS modbus")
        serial_msmodbus_parameters "${PORTNAME}"
        ;;

    "Fast CD24 in")
        serial_ll_cd24_parameters "${PORTNAME}"
        ;;

    "RTD out")
        serial_rtdout_parameters "${PORTNAME}"
        ;;

    "KVH DSP1500 Digital Gyro")
        serial_kvhdsp1500_parameters "${PORTNAME}"
        ;;
    esac

    # Write the changes to the top level config file.
    cfset ${TOPLVL_CFGFILE} function="${NEW_FUNCTION}"
    cfset ${TOPLVL_CFGFILE} baud="${NEW_BAUD}"

    # Now we've finished making any changes reset the permission on the config file
    cconf_set_perms_and_group ${TOPLVL_CFGFILE} 664 "conf"

    # Restart the service for this port
    clear
    echo Restarting ${PORTNAME} service
    svc ${PORTNAME} restart

    exit 0
done

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