#!/bin/bash

# some variables are defined in /opt/rcon/bin/sshr-base

SSHRLOCK_LOCKFILE=$LOG_DIR/lock.sshr-lock

LOCK_DIR=$LOG_DIR/locking
LOCKFILE_LINK=$LOCK_DIR/lock

INST_HBR=HBR
INST_SWA=SWA
INST_WEBSERVICE=WEBSERVICE
INST_RISO=RISO
OP_BACKUP=BACKUP
OP_RESTORE=RESTORE
OP_UPGRADE=UPGRADE
T_DATA_AP=data_ap

callback=""
force=""
instance=""
operation=""
callbackArg=""       # Session ID argument for callback 
type=""

checkLock() {
  (
    flock -w 50 -x 3
    if [ $? -ne 0 ]; then
        tracef "sshr-lock: Cannot exclusively access $SSHRLOCK_LOCKFILE"
        return 211
    fi

    if [ -f $LOCKFILE_LINK ]; then
        while [ -f $LOCKFILE_LINK ]; do
        # both symlink and regular file exist
        tracef "Both symlink $LOCKFILE_LINK and target $(readlink $LOCKFILE_LINK) exist"
        while read line; do
          tracef "$line"
        done < $LOCKFILE_LINK
        savedInstance=`getFromLock INSTANCE`
        savedCallback=`getFromLock CALLBACK`
        savedCallbackArg=`getFromLock CALLBACKARG`
        savedLockfileTarget=`getFromLock LOCKFILETARGET`
        savedPreLockfileTarget=`getFromLock PRELOCKFILETARGET`
        case $savedInstance in
            $INST_HBR) connectTo=assistant_intl;;
            $INST_SWA) connectTo=assistant_intl;;
            $INST_WEBSERVICE) connectTo=host_intl;;
            $INST_RISO) connectTo=host_intl;;
            *)
            echo "Unknown instance $savedInstance"
            return 1
            ;;
        esac
        tracef "Instance:$savedInstance Callback:$savedCallback CallbackArg:$savedCallbackArg"
        tracef "Calling $savedCallback $savedCallbackArg ..."
        output=$(/opt/rcon/bin/sshr -c $connectTo $savedCallback $savedCallbackArg 2>>$TRACEFILE)
        rc=$?
        tracef "Output: $output"
        tracef "$savedCallback $savedCallbackArg (err=$rc)"
        # rc from ssh is not sufficient, since ssh does not propagate it
        if [ $rc -eq 0 ] && [ "$output" == "0" ]; then
            # valid lock
            tracef "Valid lock"
            cat $LOCKFILE_LINK
            return 0
        elif [ $rc -ge 200 ]; then
            # sshr problem
            tracef "WARNING: Lock validity can not be checked"
            tracef "Lock supposed to be valid"
            cat $LOCKFILE_LINK
            # suppose valid lock
            return 0
        else
            # invalid lock
            tracef "Invalid lock"
            if [ -n "$savedPreLockfileTarget" ]; then
            # not the only one - locks were concatenated
            tracef "Deleting $savedLockfileTarget and updating $LOCKFILE_LINK to $savedPreLockfileTarget"
            ln -sfv $savedPreLockfileTarget $LOCKFILE_LINK >>$TRACEFILE 2>&1
            rm -f $savedLockfileTarget
            # continue with next iteration
            else
            # the only one
            tracef "Deleting $LOCKFILE_LINK and $savedLockfileTarget"
            rm -f $LOCKFILE_LINK $savedLockfileTarget
            return 1
            fi
        fi
        done
    else
        # either symlink or target file does not exist
        if [ -L $LOCKFILE_LINK ]; then
        # symlink points to nonexistent file
        # remove it, although this is critical, because it is not atomic with previous check
        tracef "Symlink $LOCKFILE_LINK points to nonexistent file"
        tracef "Deleting $LOCKFILE_LINK"
        rm -f $LOCKFILE_LINK
        return 1
        else
        tracef "Lock does not exist"
        return 1
        fi
    fi
  ) 3> $SSHRLOCK_LOCKFILE
}

createLock() {
  # call checkLock to delete unvalid lock
  tracef "Checking possible locks by calling checkLock ..."
  checkLock >/dev/null 2>>$TRACEFILE
  (
    flock -w 50 -x 3
    if [ $? -ne 0 ]; then
      tracef "sshr-lock: Cannot exclusively access $SSHRLOCK_LOCKFILE"
      return 211
    fi

    mkdir -p $LOCK_DIR
    LOCKFILE_TARGET=$LOCK_DIR/${instance}_${operation}_$$.lock

    echo "INSTANCE='$instance'" > $LOCKFILE_TARGET
    echo "OPERATION='$operation'" >> $LOCKFILE_TARGET
    echo "CALLBACK='$callback'" >> $LOCKFILE_TARGET
    echo "CALLBACKARG='$callbackArg'" >> $LOCKFILE_TARGET
    echo "TYPE='$type'" >> $LOCKFILE_TARGET
    echo "DATE='`date '+%Y%m%d%H%M%S'`'" >> $LOCKFILE_TARGET
    echo "LOCKFILETARGET='$LOCKFILE_TARGET'" >> $LOCKFILE_TARGET
    echo "PRELOCKFILETARGET=''" >> $LOCKFILE_TARGET
    ln -sv $LOCKFILE_TARGET $LOCKFILE_LINK >>$TRACEFILE 2>&1
    if [ $? -ne 0 ]; then
      tracef "Symlink $LOCKFILE_LINK exists"
      if [ -n "$ancestorLockfileTarget" ]; then
        tracef "Checking ancestor $ancestorLockfileTarget ..."
        savedLockfileTarget=`getFromLock LOCKFILETARGET`
        # the locks can be concatenated for some cases
        # check whether ancestorLockfileTarget is contained in lock chain
        workingLockfileTarget=`getFromLock LOCKFILETARGET`
        while [ -n "$workingLockfileTarget" ]; do
          if [ "$workingLockfileTarget" == "$ancestorLockfileTarget" ]; then
            tracef "Match found"
            tracef "Updating PRELOCKFILETARGET in $LOCKFILE_TARGET to $savedLockfileTarget"
            updateLock PRELOCKFILETARGET "$savedLockfileTarget" $LOCKFILE_TARGET
            ln -sfv $LOCKFILE_TARGET $LOCKFILE_LINK >>$TRACEFILE 2>&1
            cat $LOCKFILE_LINK
            tracef "Lock concatenated"
            return 0
          fi
          workingLockfileTarget=`getFromLock PRELOCKFILETARGET $workingLockfileTarget`
        done
        tracef "No match found"
      fi
      
      rm $LOCKFILE_TARGET
      cat $LOCKFILE_LINK
      tracef "Lock was not created"
      return 1
    else
      cat $LOCKFILE_LINK
      tracef "Lock created"
      return 0
    fi
  ) 3> $SSHRLOCK_LOCKFILE
}

deleteLock() {
  (
    flock -w 50 -x 3
    if [ $? -ne 0 ]; then
      tracef "sshr-lock: Cannot exclusively access $SSHRLOCK_LOCKFILE"
      return 211
    fi
  
    # check for FORCE
    if [ -n "$force" ]; then
      tracef "WARNING: Forced deleting of all locks"
      rm -fv $LOCKFILE_LINK $LOCK_DIR/*.lock >>$TRACEFILE 2>&1

      rc=$?
      tracef "Deleting lock (err=$rc)"
      return $rc
    fi
  
    # save callbackArg specified by -s. It can be empty
    specifiedCallbackArg=$callbackArg
    # save operation specified by -o. It can be empty
    specifiedOperation=$operation

    workingLockfileTarget=`getFromLock LOCKFILETARGET`
    previousLockfileTarget=$workingLockfileTarget
    while [ -n "$workingLockfileTarget" ]; do
      savedInstance=`getFromLock INSTANCE $workingLockfileTarget`
      savedCallbackArg=`getFromLock CALLBACKARG $workingLockfileTarget`
      savedOperation=`getFromLock OPERATION $workingLockfileTarget`
      savedLockfileTarget=`getFromLock LOCKFILETARGET $workingLockfileTarget`
      savedPreLockfileTarget=`getFromLock PRELOCKFILETARGET $workingLockfileTarget`
      # if callbackArg was not specified by -s take it from lock
      [ -z "$specifiedCallbackArg" ] && callbackArg=$savedCallbackArg
      # if operation was not specified by -o take it from lock
      [ -z "$specifiedOperation" ] && operation=$savedOperation

      if [ "$savedInstance" == "$instance" -a \
           "$savedCallbackArg" == "$callbackArg" -a \
           "$savedOperation" == "$operation" ]; then
        tracef "Match found for $savedLockfileTarget"
        # candidate for deleting
        if [ "$workingLockfileTarget" == "$previousLockfileTarget" ]; then
          # candidate is the first element in the list
          if [ -n "$savedPreLockfileTarget" ]; then
            # first, but not last
            tracef "It is the first, but not the last in the chain."
            tracef "Deleting $savedLockfileTarget and updating $LOCKFILE_LINK to $savedPreLockfileTarget"
            ln -sfv $savedPreLockfileTarget $LOCKFILE_LINK >>$TRACEFILE 2>&1
            rm -f $savedLockfileTarget
            rc=$?
          else
            # first and last (the only one)
            tracef "It is the first and last in the chain."
            tracef "Deleting $LOCKFILE_LINK and $savedLockfileTarget"
            rm -f $LOCKFILE_LINK $savedLockfileTarget
            rc=$?
          fi
        else
          # candidate is not the first element in the list
          tracef "It is not the first in the chain."
          tracef "Deleting $savedLockfileTarget and updating PRELOCKFILETARGET in $previousLockfileTarget to $savedPreLockfileTarget"
          updateLock PRELOCKFILETARGET "$savedPreLockfileTarget" $previousLockfileTarget
          rm -f $savedLockfileTarget
          rc=$?
        fi
        tracef "Deleting lock (err=$rc)"
        return $rc
      fi
      previousLockfileTarget=$savedLockfileTarget
      workingLockfileTarget=$savedPreLockfileTarget
    done
  
    # the whole list was explored, but no candidate was found 
    tracef "No suitable candidate was found"
    tracef "Lock was not deleted"
  ) 3> $SSHRLOCK_LOCKFILE
}

getFromLock() {
  KEY=$1
  if [ -n "$2" ]; then
    file=$2
  else
    file=$LOCKFILE_LINK
  fi
  sed -n "s/^$KEY='\(.*\)'/\1/p" $file 2>>$TRACEFILE
}

parseArgs() {
  set -- $SSH_ORIGINAL_COMMAND
  shift
  while getopts a:c:fi:o:s:t: optName; do
    case $optName in
      a) ancestorLockfileTarget=$OPTARG;;
      c) callback=$OPTARG;;
      f) force="FORCE";;
      i) instance=$OPTARG;;
      o) operation=$OPTARG;;
      s) callbackArg=$OPTARG;;       # Session ID argument for callback 
      t) type=$OPTARG;;
    esac
  done
  case $SHORTCOMMAND in
    createLock)
      if [ -z "$callback" -o -z "$instance" -o -z "$operation" -o -z "$callbackArg" ]; then
        tracef "Missing parameter. All options -c, -i, -o, -s are required"
        echo "Missing parameter. All options -c, -i, -o, -s are required"
        return 1
      fi
    ;;
    checkLock)
    ;;
    deleteLock)
      if [ -z "$instance" ]; then
        tracef "Missing parameter. Options -i is required"
        echo "Missing parameter. Options -i is required"
        return 1
      fi
    ;;
  esac
  case $SHORTCOMMAND in
    createLock|deleteLock)
      case $instance in
        $INST_HBR) ;;
        $INST_SWA) ;;
        $INST_WEBSERVICE) ;;
        $INST_RISO) ;;
        *)
          echo "Wrong instance {$INST_HBR | $INST_SWA | $INST_WEBSERVICE | $INST_RISO}"
          return 1
        ;;
      esac
    ;;
  esac
}

updateLock() {
    KEY=$1
    VAL=$2
    if [ -n "$3" ]; then
      file=$3
    else
      file=$LOCKFILE_LINK
    fi
    if grep -q "^$KEY=" $file > /dev/null 2>&1; then
      sed -i -e "s|^$KEY=.*\$|$KEY='$VAL'|" $file 2>>$TRACEFILE
    else
      echo "$KEY='$VAL'" >> $file
    fi
}

