#!/usr/bin/env bash
# shellcheck disable=SC2145
# shellcheck disable=SC2053

# ============================================================================================ #
#: Title           : systemd-vboxinit                                                          #
#: Sypnosis        : systemd-vboxinit OPTIONS                                                  #
#: Date Created    : Wed Oct  2 07:57:25 PHT 2013 / Wed Oct  2 03:11:58 UTC 2013               #
#: Last Edit       : Fri Dec 30 19:47:26 PHT 2016 / Fri Dec 30 11:47:26 UTC 2016               #
#: License         : GPLv3                                                                     #
#: Version         : 2.1.4                                                                     #
#: Author          : Jason V. Ferrer '<jetchisel@opensuse.org>'                                #
#: Description     : Auto start sessions when booting and save sessions when host is stopped.  #
#: Options         : --{about,stop,start,disable,license,help}                                 #
#: Home Page       : https://github.com/Jetchisel/VBoxAutostart                                #
#: ExtComm         : cat,find,grep,less,pgrep,systemd,VBoxManage                               #
# ============================================================================================ #

# ******************************************************************************************** #
#                                                                                              #
#              Set shell option so RunningUuids can be tested in one-line.                     #
#                                                                                              #
# ******************************************************************************************** #

shopt -s extglob

# ******************************************************************************************** #
#                                                                                              #
#   Warn and die functions, for exit messages and default status or an optional exit status.   #
#                                                                                              #
# ******************************************************************************************** #

warn() {
  printf '%s\n' "${BASH_SOURCE##*/}: $*"
}

die() {
  local st=$?
  case $2 in
    *[^0-9]*|'') :;;
    *) st=$2;;
  esac

  case $st in
    0) warn "$1" ;;
    *) warn "$1" >&2;;
  esac

  exit "$st"
}

# ******************************************************************************************** #
#                                                                                              #
#       One argument only exit immediately and avoid running the script until the end.         #
#                                                                                              #
# ******************************************************************************************** #

(( $# > 1 )) && die "Too many arguments, try --help" 1

# ******************************************************************************************** #
#                                                                                              #
#              Check if VirtualBox is installed if not exit with an error.                     #
#                                                                                              #
# ******************************************************************************************** #

NotInstalledMessage="VBoxManage is either not installed or it's not in your PATH!"

if ! type -P VBoxManage >/dev/null; then
  if ! type -P vboxmanage >/dev/null; then
    [[ -f /usr/lib/virtualbox/VBoxManage ]] || die "$NotInstalledMessage" 127
  fi
fi

# ******************************************************************************************** #
#                                                                                              #
#        Check for the required app/executable is with in your PATH, exit otherwise.           #
#                                                                                              #
# ******************************************************************************************** #

Missing=()
ExtComm=(cat find grep less pgrep systemd VBoxManage)
MissingMessage="is either not installed or it is not in your PATH!"
ExitMessage="Please install the following: "

for apps in "${ExtComm[@]}"; do
  if ! type -P "$apps" >/dev/null; then
    printf '%s %s\n' "$apps" "$MissingMessage" >&2
    Missing+=("$apps")
  fi
done

(( ${#Missing[@]} )) && die "$ExitMessage[${Missing[*]}] exiting now!" 127

# ******************************************************************************************** #
#                                                                                              #
#           Check if vboxdrv kernel module is loaded if not exit with an error.                #
#                                                                                              #
# ******************************************************************************************** #

grep -q "^vboxdrv" /proc/modules || die "vboxdrv is not loaded!"

# ******************************************************************************************** #
#                                                                                              #
#          Assign VirtualBox directory /usr/lib/virtualbox to the variable VBoxDir.            #
#                                                                                              #
# ******************************************************************************************** #

VBoxDir=/usr/lib/virtualbox

# ******************************************************************************************** #
#                                                                                              #
#  Use the VBoxManage in /usr/lib/virtualbox if it exist, else use whatever is in your PATH.   #
#                                                                                              #
# ******************************************************************************************** #

VBoxManage() {
  if [[ -f $VBoxDir/VBoxManage && -x $VBoxDir/VBoxManage ]]; then
    "${VBoxDir}"/VBoxManage "$@"
  else
    if type -P VBoxManage >/dev/null; then
      command -p VBoxManage "$@"
    else
      command -p vboxmanage "$@"
    fi
  fi
}

# ******************************************************************************************** #
#                                                                                              #
#                                  VBoxManage Functions.                                       #
#                                                                                              #
# ******************************************************************************************** #

ExtraData() {
  VBoxManage getextradata "$AllUuid" 'pvbx/startupMode'
}

ListRunningVms() {
  VBoxManage list runningvms
}

ListVms() {
  VBoxManage list vms
}

StartVms() {
  VBoxManage startvm "$AllUuid" --type headless
}

SaveVms() {
  VBoxManage controlvm "$AllUuid" savestate
}

# ******************************************************************************************** #
#                                                                                              #
#           Function to check for vms. If there is none found exit without an error.           #
#                                                                                              #
# ******************************************************************************************** #

NoVmExit() {
  mapfile -u5 -t AllVms 5< <(ListVms)
  (( ${#AllVms[@]} )) || die "No virtual machine found!"
}

# ******************************************************************************************** #
#                                                                                              #
#  Put the running/sanitized vms uuids in an array and format it in the variable RunningUuids. #
#                                                                                              #
# ******************************************************************************************** #

uuids=()
while read -u6 -r vm; do
  running_uuid=${vm##*"{"}
  uuids+=("${running_uuid%"}"*}")
done 6< <(ListRunningVms)

printf -v RunningUuids "%s|" "@(${uuids[@]})"
RunningUuids=${RunningUuids%|}

# ******************************************************************************************** #
#                                                                                              #
#  Function to sanitize/extract the vmname and uuid using P.E. inside the start,stop function. #
#                                                                                              #
# ******************************************************************************************** #

ExtractVmNameUuid() {
  AllUuid=${line##*"{"}
  AllUuid=${AllUuid%"}"*}
  VmName=${line#*'"'}
  VmName=${VmName%'"'*}
}

# ******************************************************************************************** #
#                                                                                              #
#          Function to print the vms status when started, e.g. running,auto,noauto.            #
#                                                                                              #
# ******************************************************************************************** #

VmStatus() {
  local i j k
  if (( ${#RunningVms[@]} )); then
    for i in "${!RunningVms[@]}"; do
      printf '%s\n' "Machine '${RunningVms[i]}' is already running..."
    done
  fi

  if (( ${#NoAutoRunning} )); then
    for j in "${!NoAutoRunning[@]}"; do
      printf '%s\n' "Machine '${NoAutoRunning[j]}' is already running but not on auto..."
    done
  fi

  if (( ${#NoAutoVms[@]} )); then
    for k in "${!NoAutoVms[@]}"; do
      printf '%s\n' "Machine '${NoAutoVms[k]}' is not set to auto..."
    done
  fi
}

# ******************************************************************************************** #
#                                                                                              #
#                             Function to start the vms Headless.                              #
#                                                                                              #
# ******************************************************************************************** #

start() {
  local line
  NoVmExit
  NoAutoVms=() RunningVms=() NoAutoRunning=()

  while read -u7 -r line; do
    ExtractVmNameUuid
    if [[ $(ExtraData) = *auto* && $AllUuid != $RunningUuids ]]; then
      printf '\n%s\n' "Starting Machine '$VmName'..."
      StartVms
    elif [[ $(ExtraData) != *auto* && $AllUuid = $RunningUuids ]]; then
      NoAutoRunning+=("$VmName")
    elif [[ $(ExtraData) = *auto* && $AllUuid = $RunningUuids ]]; then
      RunningVms+=("$VmName")
    elif [[ $(ExtraData) != *auto* ]]; then
      NoAutoVms+=("$VmName")
    fi
  done 7< <(ListVms)

  VmStatus
}

# ******************************************************************************************** #
#                                                                                              #
#  Function to print/list files/executables (without extension) inside /usr/lib/virtualbox.    #
#                                                                                              #
# ******************************************************************************************** #

VBoxDaemons() {
  find "$VBoxDir" -type f -iname '*v*box*' \! -name '*.*' -print
}

# ******************************************************************************************** #
#                                                                                              #
#                      Function to stop/kill all running vbox processes.                       #
#                                                                                              #
# ******************************************************************************************** #

KillAllVBoxProcess() {
  local pid pids
  while read -u9 -r daemon; do
    daemon=${daemon##*/}
    daemonpid=$(pgrep -u "$LOGNAME" -x -- "$daemon")
    (( daemonpid )) && pids+=("$daemonpid")
  done 9< <(VBoxDaemons)

  for pid in "${pids[@]}"; do
    kill -15 "$pid"
  done
}

# ******************************************************************************************** #
#                                                                                              #
#               Function to save vms state instead of shutting down completely.                #
#                                                                                              #
# ******************************************************************************************** #

stop() {
  local line
  (( ${#uuids[@]} )) || die "No virtual machine runnning!"
  while read -u8 -r line; do
    ExtractVmNameUuid
    printf '%s\n' "Saving machine '$VmName' state..."
    SaveVms
  done 8< <(ListRunningVms)

  if ! systemctl is-active vboxdrv.service >/dev/null; then
    KillAllVBoxProcess
  fi
}

# ******************************************************************************************** #
#                                                                                              #
#                             Assign UpdateMessage in an array.                                #
#                                                                                              #
# ******************************************************************************************** #

InstallMessage=(
"All VirtualBox process that is owned by $LOGNAME has been stopped."
"You can now do the following: "
"   Install/reinstall VirtualBox (Update to the latest or install an old_version.)"
"   Rebuild the VirtualBox kernel modules."
)

# ******************************************************************************************** #
#                                                                                              #
# Function to save-state of the runningvms and stop vbox daemons in preparation for an update. #
#                                                                                              #
# ******************************************************************************************** #

disable() {
  local line daemon daemonpid
  (( ${#uuids[@]} )) || die "No virtual machine runnning!"
  while read -u8 -r line; do
    ExtractVmNameUuid
    printf '%s\n' "Saving machine '$VmName' state..."
    SaveVms
  done 8< <(ListRunningVms)

  KillAllVBoxProcess && printf '\n%s\n' "${InstallMessage[@]}"
}

# ******************************************************************************************** #
#                                                                                              #
#                                       Usage Function.                                        #
#                                                                                              #
# ******************************************************************************************** #

help() {
  cat <<EOF

  Usage: ${BASH_SOURCE##*/} OPTION

  Options:
  -s, --start    Start enabled virtual machines otherwise show the state.
  -x, --stop     Save the state of all running virtual machines enabled or not.
  -d, --disable  Like stop but also stops the vbox daemons, useful before a vbox update.
  -h, --help     Show this help.
  -a, --about    A brief info.
  -l, --license  Show license.

EOF
return
}

# ******************************************************************************************** #
#                                                                                              #
#                                      About function.                                         #
#                                                                                              #
# ******************************************************************************************** #

about() {
  cat <<EOF

                         Systemd-vboxinit

  Copyright (C) 2013-2016 Jason V. Ferrer '<jetchisel@opensuse.org>'

  Auto start  sessions when  booting and save sessions  when host is
  stopped using systemd as its start up daemon.

  This  program is  free software;  you can  redistribute  it and/or
  modify  it  under the  terms  of  the  GNU  General Public License
  version 3 as published by the Free Software Foundation.

  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
  (see The LICENSE file.) along  with this program; if not, write to
  the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor
  Boston, MA  02110-1301, USA.

EOF
return
}

# ******************************************************************************************** #
#                                                                                              #
#                  License function.  (required by GPL, see the LICENSE file)                  #
#                                                                                              #
# ******************************************************************************************** #

license() {
  lisensya=/usr/share/doc/packages/systemd-vboxinit/LICENSE
  [[ -f $lisensya ]] || die "Can't find license file: $lisensya"
  less /usr/share/doc/packages/systemd-vboxinit/LICENSE
  return
}

# ******************************************************************************************** #
#                                                                                              #
#                             Check for a command line option.                                 #
#                                                                                              #
# ******************************************************************************************** #

case $1 in
  --about|-a) about
    ;;
  --disable|-d) disable
    ;;
  --help|-h) help; exit 0
    ;;
  --start|-s) start
    ;;
  --stop|-x) stop
    ;;
  --license|-l) license
    ;;
  *) help >&2; exit 1
    ;;
esac

# ============================================================================================ #
#                                                                                              #
#                                   >>> END OF SCRIPT <<<                                      #
#                                                                                              #
# ============================================================================================ #
