#!/bin/bash
#
# "©2013 Tormach® LLC. All rights reserved."
#

#
# this script is run via the autostart application setting in gnome
# this is configured from System -> Preferences -> Startup Applications
#
# it is a wrapper around starting LinuxCNC via the customized linuxcnc script
#
# it checks things before and after running linuxcnc
#
# in general, if there's an error the user can install a software update to hopefully
# fix things or if the error is severe the operator will be offered to start the remote
# support software program
#
#   are left-shift and left-alt keys pressed?
#     if yes, start gnome-panel and exit
#
#   if the file ~/config_file.txt does not have a LinuxCNC INI file path in it
#     or doesn't exist,
#     start the configuration picker application
#     after the configuration picker exits look for a configured INI file in ~/config_file.txt and start linuxcnc
#     if still not configured run the configuration application again
#
#   the file ~/config_file.txt has a LinuxCNC INI file path in it
#     if not, generate the INI file
#
#     is there a Mesa interface in this INI file?
#     if so, check that it has the correct firmware flashed into it
#     if not, display error and shut down controller
#
#     start linuxcnc script with this INI file
#       after linuxcnc exits, check for file ~/update_file.txt
#         if it exists it should have a path to an update file in the ~/updates directory
#         that file will be expanded
#         if a pre-install.sh script is in ~/tmc/scripts then it gets run
#         ~/tmc gets set to point to the version just expanded
#         if a post-install.sh script is in ~/tmc/scripts then it gets run
#
#       this script is restarted, potentially a new revision just installed by post-install.sh
#

# this points to the actual LinuxCNC install directory to be run
# it is a symbolic link to the actual install directory
SYMLINK_DIR="tmc"

# this lives in ~/tmc/scripts which is in PATH
LINUXCNC_START_SCRIPT=linuxcnc

UPDATE_FILE="$HOME/update_file.txt"
SETTINGS_RESTORE_FILE="$HOME/settings_restore_file.txt"
CONFIG_FILE="$HOME/config_file.txt"
CONFIG_PICKER="$HOME"/$SYMLINK_DIR/python/config_picker/config_picker.py

UPDATES_DIR="$HOME/updates"
# this is run if update fails to allow picking another update file
FAILED_UPDATE_SCRIPT=failed_update.py

SPLASH_PROGRAM="$HOME/$SYMLINK_DIR"/python/splash/tormach_splash.py

# logfile directory check
LOGFILES_DIR="$HOME"/gcode/logfiles
if [ ! -d $LOGFILES_DIR ]; then
  echo "Creating directory: $LOGFILES_DIR"
  mkdir -p "$LOGFILES_DIR"
fi

#uncomment this to turn off stdout buffering of Python programs
#if print() output isn't appearing until program exit
#export PYTHONUNBUFFERED=1

OPERATORLOGIN_LOG="$HOME"/gcode/logfiles/pathpilotlog.txt

# if we are here via a software update that runs a new instance of this script
# we can't kill the log redirector or we will kill this new process
# so if output redirection is already running don't do it again
echo "pgrep -c -f \"tee -a $OPERATORLOGIN_LOG\""
REDIRECTION_RUNNING=`pgrep -c -f "tee -a $OPERATORLOGIN_LOG"`
echo "REDIRECTION_RUNNING: "$REDIRECTION_RUNNING
if [ "$REDIRECTION_RUNNING" == "0" ]; then
  echo -e "$0: redirecting stdout/stderr to: ""$OPERATORLOGIN_LOG"
  # important to use tee -a to append or logrotation will create corrupted files
  exec > >(tee -a $OPERATORLOGIN_LOG) 2>&1
else
  echo "stdout/stderr redirection already in progress"
fi

# logrotate config and status files
LOGROTATE_MINSIZE=100k
LOGROTATE_STATUS="$LOGFILES_DIR"/logrotate.status
LOGROTATE_CONF="$LOGFILES_DIR"/logrotate.conf
LOGROTATE_CONF_DATA=""$LOGFILES_DIR"/pathpilotlog.txt {
  # at least this size before rotating
  minsize "$LOGROTATE_MINSIZE"
  # copy before truncating
  copytruncate
  # no rotate empty log file
  notifempty
  # permissions of new file
  create 644 "$USER" "$USER"
  # leave new file and 2 previous files
  rotate 2
}"
echo "$LOGROTATE_CONF_DATA" > "$LOGROTATE_CONF"

# set flag to indicate if this script is running from Autostart (no terminal) or from a terminal
# if running from terminal the behavior is slightly different
RUNNING_FROM_TTY=0
if tty -s; then
  RUNNING_FROM_TTY=1
fi

# echos $1 first argument to terminal
function tty_echo () {
  #if [ $RUNNING_FROM_TTY -ne 0 ]; then
    echo -e "$1"
  #fi
}

# computer information
LOGFILES_DIR="$HOME"/gcode/logfiles
SYSTEM_INFO="$LOGFILES_DIR"/systeminfo.txt
rm -f "$SYSTEM_INFO"
touch "$SYSTEM_INFO"
echo "lshal | grep ' system.'" >> "$SYSTEM_INFO"
lshal | grep " system." >> "$SYSTEM_INFO"
echo "" >> "$SYSTEM_INFO"
echo "lspci" >> "$SYSTEM_INFO"
lspci >> "$SYSTEM_INFO"
echo "" >> "$SYSTEM_INFO"
echo "lsusb" >> "$SYSTEM_INFO"
lsusb >> "$SYSTEM_INFO"
echo "" >> "$SYSTEM_INFO"
echo "lshal" >> "$SYSTEM_INFO"
lshal >> "$SYSTEM_INFO"

# run rip-environment script
RIP_ENVIRONMENT_SCRIPT="$HOME/$SYMLINK_DIR"/scripts/rip-environment.sh
tty_echo "sourcing $RIP_ENVIRONMENT_SCRIPT $HOME/$SYMLINK_DIR"
source $RIP_ENVIRONMENT_SCRIPT "$HOME/$SYMLINK_DIR"

# calibrate touchscreen as needed
tty_echo "setting touchscreen calibration"
xinput_calibrator_pointercal.sh

LOGROTATE_SLEEP=3600
echo "starting log rotator"
# seconds to sleep between log rotations, path to config file, path to status file
echo "logrotator.sh $LOGROTATE_SLEEP "$LOGROTATE_CONF" "$LOGROTATE_STATUS""
logrotator.sh $LOGROTATE_SLEEP "$LOGROTATE_CONF" "$LOGROTATE_STATUS" &
# remember PID so it can be killed on exit
LOGROTATOR_PID=$!
echo "LOGROTATOR_PID: "$LOGROTATOR_PID

SHIFTTEST_PROG="$HOME/$SYMLINK_DIR/bin/shifttest"
FILE_OWNER_PERMS=`stat -c "%U%a" $SHIFTTEST_PROG`
if [ ! "$FILE_OWNER_PERMS" == "root4755" ]; then
  # fix the permissions
  fileperms module 4755 "$SHIFTTEST_PROG" > /dev/null 2>&1
  if [ ! $? = 0 ]; then
    tty_echo "Failed to set permissions on $SHIFTTEST_PROG"
  fi
fi

# PathPilot DVD image <= v1.6 does not have gnome-power-manager enabled at startup
# start gnome-power-manager if not already running
PIDOF_XFCE_PM=`pidof xfce4-power-manager`
# do not start it if xfce-power-manager is runnning
if [ $? != 0 ]; then
  PIDOF_GNOME_PM=`pidof gnome-power-manager`
  if [ $? != 0 ]; then
    echo "gnome-power-manager not running"
    GNOME_PM=`which /usr/bin/gnome-power-manager`
    if [ -x $GNOME_PM ]; then
      echo "starting $GNOME_PM"
      $GNOME_PM &
    fi
  else
    echo "gnome-power-manager already running"
  fi
else
  echo "xfce4-power-manager already running"
fi


# delete this gconf file to prevent the "slow keys" "feature" from staying enabled should it
# accidentally get enabled from pressing the shift key too long
KBDFILE="$HOME"/.gconf/desktop/gnome/peripherals/keyboard/host-`hostname`/0/%gconf.xml
if [ -e "$KBDFILE" ]; then
  rm "$KBDFILE"
fi

function start_gnome_panel {
    GNOME_PANEL_PID=`pidof gnome-panel`
    if [ "$GNOME_PANEL_PID" == "" ]; then
      tty_echo "starting gnome-panel"
      gnome-panel &
    fi
}

# if left shift and alt keys are pressed start gnome-panel and exit
function check_shift_alt {
  # check for left-shift and left-alt pressed
  `shifttest lshift`
  LEFT_SHIFT=$?
  `shifttest lalt`
  LEFT_LALT=$?
# hash the next line to force a break out to gnome at startup 
  if [ "$LEFT_SHIFT" == "1" ] && [ "$LEFT_LALT" == "1" ]; then
    # both are pressed
    start_gnome_panel
    exit 0
  fi
}

# if not running from a terminal force screen resolution
if [ $RUNNING_FROM_TTY -eq 0 ]; then
  # force screen resolution to match UI, sometimes the display comes up as 1280x1024
  xrandr -s 1024x768
fi

# this will not return if keys pressed
# it will start a gnome-panel and exit to the desktop
check_shift_alt

SHUTDOWN_PROG=`which shutdown`

# use of "gnome-power-cmd.sh shutdown" deprecated
# actually shutting the power off via shutdown has a bad side effect at least with some PCs
# that they won't automatically power up when AC power is reapplied
# instead of shutting down we will merely HALT and leave the power on
# a shutdown (plymouth) splash screen indicates safe to remove power
# AC power is switched at the electrical panel main disconnect
# /sbin/shutdown must be setuid root to work
# this leaves the shutdown screen displayed and the the system halted
SHUTDOWN_COMMAND="$SHUTDOWN_PROG -H now"

function shutdown_controller {
  tty_echo "terminating log rotator and subprocesses"
  killtree.sh $LOGROTATOR_PID KILL
  # if running from a terminal exit without shutdown and start the gnome-panel
  if tty -s; then
    start_gnome_panel
    #tty_echo "\n\nExiting script instead of shutting down controller . . .\n"
    exit 0
  fi
  # allow override of shutdown if not in a terminal
  sleep 2
  check_shift_alt
  $SHUTDOWN_COMMAND
}

# this tells us if gnome is running (always in the Ubuntu 10.04 based image)
# so we can display dialog boxes via zenity
# if no gnome then echo to terminal if present
GNOME_COUNT=`w -s | grep gnome | wc -l`
if [ "${GNOME_COUNT}" -gt 0 ]; then
  HAVE_GNOME=1
else
  HAVE_GNOME=0
fi
echo "HAVE_GNOME: " $HAVE_GNOME

# display error message passed as argument $1
# blocks until OK pressed
function popup_error () {

  if [ "${HAVE_GNOME}" -gt 0 ]; then
    # this will block until OK is pressed
    zenity --error --text "$1"
  else
    tty_echo "$1"
    read -p "Press any key to continue . . ." -n1 -s
  fi
}

# display infromational message passed as argument $1
# blocks until OK pressed
function popup_info () {

  if [ "${HAVE_GNOME}" -gt 0 ]; then
    # this will block until OK is pressed
    zenity --info --text "$1"
  else 
    tty_echo "$1"
    read -p "Press any key to continue . . ." -n1 -s
  fi
}

# display passed message in $1
# ask operator if he wants to start the remote support client
# OK starts remote support
# Cancel shuts down controller
function run_remote_support () {

  if [ "${HAVE_GNOME}" -gt 0 ]; then
    zenity --question --text "$1\nStart remote support client software?"
    if [ $? = 0 ]; then
      # start remote support program
      # must use nohup or teamviewer will exit after this script exits
      nohup teamviewer > /dev/null 2>&1 &
    fi
  else
    read -p "Press 'Y' to start remote client software. Press any other key to continue." -n1 -s
    if [[ $REPLY =~ ^[Yyy]$ ]]; then
      echo
      tty_echo "Starting remote client software"
      # must use nohup or teamviewer will exit after this script exits
      nohup teamviewer > /dev/null 2>&1 &
    fi
  fi
  shutdown_controller
}

# runs the 'failed update' script tp pick and install another version
# if install fails or no version is chosen to install
# the operator if offered to start the remote support software
function select_and_install_update {
  # run program to allow install of different revision
  $FAILED_UPDATE_SCRIPT
  check_for_update
  # this script is restarted by check_for_update if update installed
  # in other words, if an update is not installed because it failed or none
  # was selected the following code will be reached
#  run_remote_support
}

# 'inivar' is in ~/$SYMLINK_DIR/bin which is in the PATH
INIVAR=inivar
# this gets filled with the contents of $CONFIG_FILE
INIFILE=
# retrieved string lives here
retvar=

# helper function used primarily to retrieve the name of the Mesa .bit file
# from the current INI file
function get_from_INI_file {
    # $1 variable
    # $2 section of INIFILE
    #var_name=$1
    retvar=
    retvar=`$INIVAR -ini "$INIFILE" -var $1 -sec $2 2> /dev/null`
    tty_echo "[$2] $1 = '$retvar'"
}

# Note that mesaflash is built from source and installed to the tmc bin folder
#MESAFLASH=`which mesaflash`

# if it fails all that can be done is to try installing a different version of the UI

#function check_mesaflash_permissions {
#  # program file must be setuid root to access Mesa hardware
#  # this checks and sets file permissions if needed

#  if [ -e $MESAFLASH ]; then
#      FILE_OWNER_PERMS=`stat -c "%U%a" $MESAFLASH`
#      tty_echo "$MESAFLASH: $FILE_OWNER_PERMS"
#      if [ ! "$FILE_OWNER_PERMS" == "root4755" ]; then
#          # fix the permissions
#          fileperms module 4755 "$MESAFLASH" > /dev/null 2>&1
#          if [ ! $? = 0 ]; then
#              popup_error "Failed to set permissions of $MESAFLASH.\n\nInstall a software update."
#              select_and_install_update
#              # does not return
#          fi
#      fi
#  else
#    # program file does not exist
#    # install update or shutdown
#    popup_error "Utility '$MESAFLASH' program file not found.\n\nInstall a software update."
#    select_and_install_update
#    # does not return
#  fi
#}


# This will verify the first portion of the .bit file and if it fails to verify it will flash the card

# lock file to synchronize popups with this script
#MESAFLASH_LCK=/tmp/mesaflash.lck

#function FlashMesaCard {
#    # existance of config file and INI file already verified
#    INIFILE=`cat "$CONFIG_FILE"`


#    # fetch the name of the .bit file from the HOSTMOT2 section of the INI file
#    get_from_INI_file BITFILE0 HOSTMOT2
#    if [ -z $retvar ]; then
#      # empty response, BITFILE0 or HOSTMOT2 entry missing in INI?
#      # then assume this is a sim configuration INI file
#      tty_echo "[HOSTMOT2] BITFILE0 entry not found in INI file."
#      tty_echo "Assuming sim config."
#      return
#    fi 
#    BITFILE0=${EMC2_HOME}/$retvar

#    # check that .bit file exists
#    if [ ! -e $BITFILE0 ]; then
#      # .bit file does not exist
#      popup_error "Mesa interface firmware file '$BITFILE0' not found.\n\nInstall a software update."
#      select_and_install_update
#      # does not return
#    fi

#    # verify the first chunk of the .bit file with the Mesa card
#    # if it verifies as the same, then do nothing
#    tty_echo "[HOSTMOT2] BITFILE0 = $BITFILE0"

#    # halt if Mesa card not detected by lspci
#    LSPCI_MESA=`lspci -n -d 2718:5125`
#    tty_echo "lspci -n -d 2718:5125: $LSPCI_MESA"

#    # response will be empty or something like: "03:01.0 1100: 2718:5125"
#    if [ "$LSPCI_MESA" == "" ]; then
#      # cannot procede
#      popup_error "Mesa interface card not detected in controller.\n\nController will shut down."
#      shutdown_controller
#    fi

#    # verify permissions
#    check_mesaflash_permissions

#    # run the flashing utility to compare card to .bit file
#    $MESAFLASH --device 5i25 --verify $BITFILE0 > /dev/null 2>&1
#    if [ $? = 0 ]; then
#      # no need to flash it
#      return 0
#    fi

#    # card flash did not match .bit file
#    if [ "${HAVE_GNOME}" -gt 0 ]; then
#      zenity --info --text "The Mesa interface requires reprogramming with new firmware. Click OK to proceed."
#      # run mesaflash in gnome-terminal
#      # mesaflash doesn't return an error code if it fails to write
#      # until there is time to fix that we leave the terminal open for user verification
#      touch $MESAFLASH_LCK
#      gnome-terminal -x "bash" -c "echo 'Reprogramming Mesa interface'; $MESAFLASH --device 5i25 --write $BITFILE0; read -n1 -s -p 'press a key to close this terminal window . . .'; rm $MESAFLASH_LCK"
#      # wait for lock file to go away before displaying shutdown message
#      while true; do
#        sleep 1
#        if [[ ! -e $MESAFLASH_LCK ]]; then
#          break
#        fi
#      done
#      zenity --info --text "New Mesa firmware loaded.\n\nPress OK to shut down controller. After display indicates safe to power down, power cycle controller to load new Mesa firmware."
#    else
#      read -p "The Mesa interface needs to be reprogrammed. Press any key to continue . . ." -n1 -s
#      tty_echo "running $MESAFLASH $BITFILE0 write"
#      bash -c "$MESAFLASH $BITFILE0 write"
#      tty_echo "\nController will now shut down.\n\nAfter controller has halted power cycle controller to load new Mesa firmware.\n"
#      read -p "Press any key to shut down . . ." -n1 -s
#    fi

#    shutdown_controller
#}


function check_for_settings_restore {
    # test for settings restore required
    # then look for backup file with zip file path in it
    # presence of this file indicates settings restore requested
    if [ -e "$SETTINGS_RESTORE_FILE" ]; then
      tty_echo "Settings restore in progress."
      tty_echo "Found restore file file: $SETTINGS_RESTORE_FILE"
      SETTINGS_RESTORE_FILE_PATH=`cat "$SETTINGS_RESTORE_FILE"`
      # remove this file as soon as we are done with it to prevent infinite loop if there's a problem
      tty_echo "rm -f "$SETTINGS_RESTORE_FILE""
      rm -f "$SETTINGS_RESTORE_FILE"
      if [ -e "$SETTINGS_RESTORE_FILE_PATH" ]; then
        FILE_TYPE=`file -ib "$SETTINGS_RESTORE_FILE_PATH"`
        tty_echo "file -ib "$SETTINGS_RESTORE_FILE_PATH""
        tty_echo "FILE_TYPE: "$FILE_TYPE
      else
          popup_error "Settings Backup File "$SETTINGS_RESTORE_FILE_PATH" not found.\n"n
      fi
      tty_echo "unzip -o $SETTINGS_RESTORE_FILE_PATH"
      unzip -o "$SETTINGS_RESTORE_FILE_PATH"
    fi
}


function check_for_update {
    # test for software update installation required
    # then look for update file with tarball file path in it
    # presence of this file indicates software update requested
    if [ -e "$UPDATE_FILE" ]; then

      tty_echo "Software update in progress."
      #if [ "${HAVE_GNOME}" -gt 0 ]; then
      #  # start a zenity progress dialog
      #  zenity --text="Installing software update" --progress --pulsate --auto-close &
      #  PROGRESS_PID=$!
      #  echo_tty $PROGRESS_PID
      #fi

      tty_echo "Found software update file: $UPDATE_FILE"

      # update file present, check contents
      LCNC_UPDATE_FILE_PATH=`cat "$UPDATE_FILE"`
      tty_echo "Using update tarball: "$LCNC_UPDATE_FILE_PATH
      # remove this file as soon as we are done with it to prevent infinite loop if there's a problem
      tty_echo "rm -f "$UPDATE_FILE""
      rm -f "$UPDATE_FILE"

      TGZ_UPDATE_FILE_PATH="${LCNC_UPDATE_FILE_PATH%.*}.tgz"
      tty_echo "destination tar gzip file: "$TGZ_UPDATE_FILE_PATH""

      if [ -e "$LCNC_UPDATE_FILE_PATH" ]; then
        gpg --yes --no-use-agent --decrypt --passphrase=moravianvalley --output="$TGZ_UPDATE_FILE_PATH" "$LCNC_UPDATE_FILE_PATH"
        # check exit code of gpg
        if [ ! $? = 0 ]; then
          popup_error "File "$LCNC_UPDATE_FILE_PATH" did not decrypt properly.\n\nInstall another software update."
          select_and_install_update
          # does not return
        fi
      else
        popup_error "Update file "$LCNC_UPDATE_FILE_PATH" not found.\n\nInstall another software update."
        select_and_install_update
        # does not return
      fi

      if [ -e "$TGZ_UPDATE_FILE_PATH" ]; then
        FILE_TYPE=`file -ib "$TGZ_UPDATE_FILE_PATH"`
        tty_echo "file -ib "$TGZ_UPDATE_FILE_PATH""
        tty_echo "FILE_TYPE: "$FILE_TYPE
      else
          popup_error "Decrypted File "$TGZ_UPDATE_FILE_PATH" not found.\n\nInstall another software update."
          select_and_install_update
          # does not return
      fi

      # test if really a gzipped file
      if [[ "$FILE_TYPE" == *"gzip"* ]]; then
        # extract tarball
        tty_echo "tar xfz "$TGZ_UPDATE_FILE_PATH""
        tar xfz "$TGZ_UPDATE_FILE_PATH"
        # check exit code of tar
        if [ ! $? = 0 ]; then
          popup_error "File "$TGZ_UPDATE_FILE_PATH" did not extract properly.\n\nInstall another software update."
          select_and_install_update
          # does not return
        fi
      else
          popup_error ""$TGZ_UPDATE_FILE_PATH" is not a gzipped file.\n\nInstall another software update."
          select_and_install_update
          # does not return
      fi

      # there will/should be a trailing '/' in $UPDATE_DIR 
      # test that directory exists before moving $SYMLINK_DIR to new directory
      # extract base file name from first line of tarball file listing
      UPDATE_DIR=`cat "$TGZ_UPDATE_FILE_PATH" | tar tz | head -1`
      rm "$TGZ_UPDATE_FILE_PATH"
      tty_echo "Update directory is: $UPDATE_DIR"

      if [ -d "$HOME"/"$UPDATE_DIR" ]; then
        # run the preinstall.sh script if it exists
        if [ -x "$HOME"/"$UPDATE_DIR"/scripts/preinstall.sh ] ; then
          tty_echo "executing preinstall script in "$HOME"/"$UPDATE_DIR"/scripts/preinstall.sh"
          "$HOME"/"$UPDATE_DIR"/scripts/preinstall.sh
        fi

        # find path of current bitfile from INI file
        INIFILE=`cat "$CONFIG_FILE"`
        eval INIFILE=$INIFILE
        get_from_INI_file BITFILE0 HOSTMOT2
        CUR_BITFILE0=${EMC2_HOME}/$retvar
        tty_echo "current bitfile $CUR_BITFILE0"
        # replace 'tmc' with name of update directory
        NEWINIFILE=${INIFILE//$SYMLINK_DIR/$UPDATE_DIR}
        # set new INIFILE
        eval INIFILE=$NEWINIFILE

        # if $SYMLINK_DIR is not a symlink we're really running in test/development mode
        # in this case just delete the directory and contents and rename the
        # new directory to $SYMLINK_DIR
        if [ ! -h "$SYMLINK_DIR" ]; then
          tty_echo "$SYMLINK_DIR is not a symlink"
          tty_echo "rm -f "$SYMLINK_DIR""
          rm -rf "$SYMLINK_DIR"
          tty_echo "mv "$HOME"/"$UPDATE_DIR" "$SYMLINK_DIR""
          mv "$HOME"/"$UPDATE_DIR" "$SYMLINK_DIR"
        else
          # move the symlink to the new directory
          tty_echo "rm -f "$SYMLINK_DIR""
          rm -f "$SYMLINK_DIR"
          tty_echo "ln -s "$UPDATE_DIR" "$SYMLINK_DIR""
          ln -s "$UPDATE_DIR" "$SYMLINK_DIR"
        fi
        # run the postinstall.sh script if it exists
        if [ -x "$SYMLINK_DIR"/scripts/postinstall.sh ] ; then
          tty_echo "executing postinstall script in "$SYMLINK_DIR"/scripts/postinstall.sh"
          "$SYMLINK_DIR"/scripts/postinstall.sh
        fi

        tty_echo "terminating log rotator and subprocesses"
        killtree.sh $LOGROTATOR_PID KILL
        # start this script over again running the new version installed by postinstall.sh
        exec $0
        echo "should never get here after exec!"
        exit 0

      else
        # this should never happen - the expected update directory didn't get created by untarring update
        # it could happen from a packaging mistake in the update file
        popup_error "New directory for update files does not exist: $UPDATE_DIR.\n\nInstall a software update."
        select_and_install_update
        # does not return
      fi
    else
      tty_echo "File "$UPDATE_FILE" not found. No software update to perform."
    fi
}

SHUTDOWN_PROG=`which shutdown`
FILE_OWNER_PERMS=`stat -c "%U%a" $SHUTDOWN_PROG`
if [ ! "$FILE_OWNER_PERMS" == "root4755" ]; then
  # fix the permissions
  fileperms module 4755 "$SHUTDOWN_PROG" > /dev/null 2>&1
  if [ ! $? = 0 ]; then
    tty_echo "Failed to set permissions on $SHUTDOWN_PROG"
  fi
fi

TIME_ADMIN_PROG="$HOME/$SYMLINK_DIR/bin/time-admin-wrapper"
FILE_OWNER_PERMS=`stat -c "%U%a" $TIME_ADMIN_PROG`
if [ ! "$FILE_OWNER_PERMS" == "root4755" ]; then
  # fix the permissions
  fileperms module 4755 "$TIME_ADMIN_PROG" > /dev/null 2>&1
  if [ ! $? = 0 ]; then
    tty_echo "Failed to set permissions on $TIME_ADMIN_PROG"
  fi
fi

# make sure there is an updates directory
if [ ! -d "$UPDATES_DIR" ]; then
  mkdir -p "$UPDATES_DIR"
fi

# make sure there is a symlink in ~/updates to /media so $FAILED_UPDATE_SCRIPT can get to the USB stick
if [ ! -d "$UPDATES_DIR/USB" ]; then
  ln -s "/media" "$UPDATES_DIR/USB"
fi

# main loop
while true; do

  # look for configuration file with LinuxCNC INI file path in it
  if [ ! -e "$CONFIG_FILE" ]; then
    # configuration file not found
    # run configuration picker
    # don't want our customers to see this error message - just show the config picker
    #popup_info "Configuration file $CONFIG_FILE not found."
    # run configuration picker
    $CONFIG_PICKER
    CONFIG_PICKER_RESULT=$?
    if [ $CONFIG_PICKER_RESULT == 1 ]; then
      # User pressed exit button - don't launch config picker in endless loop
      tty_echo "Config picker exited when user pressed exit button"
      shutdown_controller
    fi
    # now try again
    continue
  else
    # config file present, check contents
    LCNC_INI_FILE_PATH=`cat "$CONFIG_FILE"`
    tty_echo "INI file: $LCNC_INI_FILE_PATH"

    # does the string end in ".ini"?
    PATH_LENGTH=${#LCNC_INI_FILE_PATH}
    SEARCH_POS=`expr $PATH_LENGTH - 3`
    FILE_NAME_ENDS_WITH=`expr substr $LCNC_INI_FILE_PATH $SEARCH_POS 4`

    if [ ! "$FILE_NAME_ENDS_WITH" == ".ini" ]; then
      # run configuration picker
      popup_info "$LCNC_INI_FILE_PATH does not end with '.ini'."
      # run config picker
      $CONFIG_PICKER
      # now try again
      continue
    fi

    INIFILE=`cat "$CONFIG_FILE"`    
    
    # verify that we're looking at the one in 'tmc', not elsewhere
    if [[ $INIFILE != *~/tmc* ]]; then
      popup_info "$LCNC_INI_FILE_PATH does not start with '~/tmc' Will run config picker."
      # run config picker
      $CONFIG_PICKER
      continue
    fi

    
    # verify INI file exists
    # expand "~/"
    EXPANDED_INIFILE=`eval echo $INIFILE`
    tty_echo $EXPANDED_INIFILE

    if [ ! -e $EXPANDED_INIFILE ]; then
      # INI file does not exist
      # generate it from source
      echo "about to run: makeini.py $EXPANDED_INIFILE"
      # iniparse python module not installed on initial controller image
      # this will check for it explicitly
      python -c 'import iniparse'
      if [ ! $? = 0 ]; then
        popup_error "Python module $1 not installed. Install package 'python-iniparse' or re-image controller with newer version."
        continue
      fi
      makeini.py "$EXPANDED_INIFILE"
      if [ ! $? = 0 ]; then
        # error
        popup_info "Something went wrong generating the INI file: $EXPANDED_INIFILE"
        # run config picker
        $CONFIG_PICKER
        # now try again
        continue
      fi
    fi

    # verify the Mesa card has the correct firmware, program it if not
#    FlashMesaCard
    # does not return if card gets reprogrammed - power cycle required - NOT JUST A REBOOT

    # Mesa card configured OK - run LinuxCNC
    tty_echo "$LINUXCNC_START_SCRIPT $LCNC_INI_FILE_PATH"

    # setup environment
    . "$HOME/$SYMLINK_DIR"/scripts/rip-environment.sh "$HOME/$SYMLINK_DIR"

    # start the screen splash
    "$SPLASH_PROGRAM" 2  &
    SPLASH_PID=$!

    # start linuxcnc
    $LINUXCNC_START_SCRIPT $LCNC_INI_FILE_PATH
    UI_RESULT=$?
    tty_echo "UI exit code: $UI_RESULT"
    if [ $UI_RESULT == 0 ]; then
      # UI exited without error - shutdown controller
      tty_echo "UI exited OK"
      # continue after if/else to update or shutdown
    elif [ $UI_RESULT == 137 ]; then
      # result code '137' means the UI python process was killed
      # exit normally
      tty_echo "UI process was killed"
      # continue after if/else to update or shutdown
    elif [ $UI_RESULT == 11 ]; then
       # result code '11' means the operator wants to pick a new machine configuration
       # the UI has changed the INI file name
       # restart the main loop of this script
       tty_echo "Switching configuration - calling config_picker"
       # run config picker
       $CONFIG_PICKER
       # now try again
       continue
    elif [ $UI_RESULT == 12 ]; then
      # result code '12' means the operator is switching from mill to duality mode (mill with lathe UI)
      # the UI has changed the INI file name
      # restart the main loop of this script
      tty_echo "Switching from mill to duality mode"
      continue
    elif [ $UI_RESULT == 13 ]; then
      # result code '13' means the operator is switching from duality mode to mill (mill with lathe UI)
      # the UI has changed the INI file name
      # restart the main loop of this script
      tty_echo "Switching from duality to mill mode"
      continue
    elif [ $UI_RESULT == 14 ]; then
      # result code '14' means we exited due to a settings restore request
      # look for the name of the restore file and unzip it, then restart
      check_for_settings_restore
      # restart the main loop of this script
      tty_echo "Restarting UI after restoring settings request"
      continue
    else
      # anything other than the above is an error
      # kill splash screen in case UI didn't kill it
      tty_echo "bad exit code killing SPLASH"
      tty_echo "killtree.sh $SPLASH_PID TERM"
      killtree.sh $SPLASH_PID TERM
      popup_error "Machine control software experienced an error.\n\nInstall a software update."
      select_and_install_update
      # this will not return
    fi

    # LinuxCNC finished without error, see if new revision needs installation
#    check_for_update
    # will not return if an update is installed - this script will restart

    shutdown_controller
    # will not return, controller will either shut down or script will exit

    # just in case shutdown_controller fails and return start gnome-panel and exit
    start_gnome_panel
    exit 1
  fi

# end of while loop
done

