#!/usr/bin/bash
#
# Stop virtual system
# Copyright (C) 2013-2024 by Thomas Dreibholz
# Copyright (C) 2014 by Forough Golkar <forough.golkar87@gmail.com>
# Copyright (C) 2013 by Nima Fallah Darya <nima.darya@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: dreibh@simula.no

# Bash options:
set -e


# ###### Call VBoxManage with timeouts #######################################
vmcontrol ()
{
   intTimeout=45
   killTimeout=60
   sudo -u $NorNet_LocalNode_NorNetUser timeout -s INT -k $killTimeout $intTimeout VBoxManage controlvm $@ || true
}


# ###### Wait for shutdown ##################################################
wait_for_shutdown ()
# $1 = Name
# $2 = Timeout
{
   echo -n "Timeout=$2s: 0s"
   i=0
   while [ $i -lt $2 ] ; do
      echo -n "."
      sleep 1
      let i=$i+1
      let r=($i % 10)
      if [ $r -eq 0 ] ; then
         echo -n "${i}s"
      fi
      status=`Check-VSystem "$MACHINE_NAME"`
      if [ "$status" = "aborted" -o "$status" = "poweroff" -o "$status" = "stuck" -o "$status" = "stuck" ] ; then
         echo -n " OK!"
         break
      elif [ "$status" = "BAD_MACHINE" ] ; then
         echo -n " HANGING?"
         break
      fi
      # Again send ACPI powerbutton.
      # If the system had been booting, the event might have been missed.
      if [ "$status" = "running" ] ; then
         if [ "$NorNet_Server_VirtualizationSystem" = "" -o "$NorNet_Server_VirtualizationSystem" = "VirtualBox" ] ; then
            vmcontrol "$1" acpipowerbutton
         elif [ "$NorNet_Server_VirtualizationSystem" = "KVM" ] ; then
            virsh shutdown "$1" >/dev/null
         fi
      fi
   done || true
   echo ""
}



# ###### Main program #######################################################
if [ $# -lt 1 ] ; then
   echo >&2 "Usage: $0 VM_name [stop_delay]"
   exit 1
fi

NORNET_DIRECTORY="/etc/nornet"

# ====== Get NorNet user name ===============================================
if [ -e $NORNET_DIRECTORY/nornetapi-config ] ; then
   . $NORNET_DIRECTORY/nornetapi-config
elif [ -e ./nornetapi-config ] ; then
   . ./nornetapi-config
fi
if [ "$NorNet_LocalNode_NorNetUser" = "" ] ; then
   NorNet_LocalNode_NorNetUser="nornetpp"
fi

MACHINE_NAME="$1"
if [ "$2" != "" ] ; then
   STOPDELAY="$2"
else
   STOPDELAY=0
fi


# ====== Perform shutdown ===================================================
if [ "$NorNet_Server_VirtualizationSystem" = "" -o "$NorNet_Server_VirtualizationSystem" = "VirtualBox" ] ; then
   status=`Check-VSystem "$MACHINE_NAME"` && \
   if [ "$status" = "paused" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Resuming $MACHINE_NAME to perform shutdown ..." && \
      vmcontrol "$MACHINE_NAME" resume
      status=`Check-VSystem "$MACHINE_NAME"`
   fi

   if [ "$status" = "running" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Stopping $MACHINE_NAME (ACPI power button) ..." && \
      vmcontrol "$MACHINE_NAME" acpipowerbutton && \
      wait_for_shutdown "$MACHINE_NAME" "$STOPDELAY"
      status=`Check-VSystem "$MACHINE_NAME"`
   fi

   if [ "$status" = "running" -o "$status" = "stopping" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Stopping $MACHINE_NAME (power off) ..."
      if [ "$status" = "running" ] ; then
         vmcontrol "$MACHINE_NAME" poweroff
      fi
      wait_for_shutdown "$MACHINE_NAME" "$STOPDELAY"
   fi

elif [ "$NorNet_Server_VirtualizationSystem" = "KVM" ] ; then
   status=`Check-VSystem "$MACHINE_NAME"` && \
   if [ "$status" = "paused" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Resuming $MACHINE_NAME to perform shutdown ..." && \
      virsh resume "$MACHINE_NAME"
      status=`Check-VSystem "$MACHINE_NAME"`
   fi

   if [ "$status" = "running" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Stopping $MACHINE_NAME (ACPI power button) ..." && \
      virsh shutdown "$MACHINE_NAME" && \
      wait_for_shutdown "$MACHINE_NAME" "$STOPDELAY"
      status=`Check-VSystem "$MACHINE_NAME"`
   fi

   if [ "$status" = "running" -o "$status" = "stopping" ] ; then
      echo "`env LANG=C date +%FT%H:%M:%S`: Stopping $MACHINE_NAME (power off) ..."
      if [ "$status" = "running" ] ; then
         virsh destroy "$MACHINE_NAME"
      fi
      wait_for_shutdown "$MACHINE_NAME" "$STOPDELAY"
   fi

   rm -f "/var/run/nornet-server-$MACHINE_NAME.pid"

else
   echo >&2 "ERROR: Bad setting $NorNet_Server_VirtualizationSystem for NorNet_Server_VirtualizationSystem!"
   exit 1
fi


# ====== Work around for VirtualBox problem =================================
if [ "$NorNet_Server_VirtualizationSystem" = "" -o "$NorNet_Server_VirtualizationSystem" = "VirtualBox" ] ; then
   # Check whether the VirtualBox machine hangs.
   # This seems to be a common and yet unfixed bug of VirtualBox ...
   if [ -e "/var/run/nornet-server-$MACHINE_NAME.pid" ] ; then
      pid=`cat "/var/run/nornet-server-$MACHINE_NAME.pid"`
      if [ "$pid" != "" ] ; then
         if ps -p $pid >/dev/null 2>&1 ; then
            echo "`env LANG=C date +%FT%H:%M:%S`: $MACHINE_NAME seems to be BROKEN! Scheiße!"
            ps -p $pid || true
            echo "Sending SIGKILL to $pid ..."
            sudo -u $NorNet_LocalNode_NorNetUser kill -KILL $pid || true
         fi
      fi
      rm -f "/var/run/nornet-server-$MACHINE_NAME.pid"
   fi
fi
