#!/bin/bash
#
# shellcheck disable=SC2181
#
# Copyright (c) 2022-2026 openmediavault plugin developers
#
# This file is licensed under the terms of the GNU General Public
# License version 2. This program is licensed "as is" without any
# warranty of any kind, whether express or implied.
#
# version: 0.0.1
#

if [[ $(id -u) -ne 0 ]]; then
  echo "This script must be executed as root or using sudo."
  exit 99
fi

export LC_ALL=C.UTF-8

# declare/initialize
cloneVm=""
sourceVm=""

# logging location
logDir="/var/log/"
logFile="${logDir}/omv-backup-vm.log"


_log()
{
  msg=${1}
  echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [linkedclone] ${msg}" | tee -a ${logFile} >&2
}

# loop through options
while getopts "c:hv:" opt; do
  case "${opt}" in
    c)
      cloneVm=${OPTARG}
      _log "Clone VM :: ${cloneVm}"
      ;;
    h)
      echo "Use the following flags:"
      echo "  -h"
      echo "    show this help"
      echo "  -c clone vm-name"
      echo "    VM name for linked clone"
      echo "  -v source vm-name"
      echo "    VM name to clone"
      echo ""
      echo "Examples:"
      echo "  omv-linked-clone -c testvm1clone -v testvm1"
      echo ""
      exit 100
      ;;
    v)
      sourceVm=${OPTARG}
      _log "Source VM :: ${sourceVm}"
      ;;
    \?)
      echo "Invalid option: -${OPTARG}"
      ;;
  esac
done

if [ ! -f "/usr/bin/virsh" ]; then
  _log "virsh command not found!"
  exit 2
fi

# check if VM name was specified
if [ -z "${cloneVm}" ]; then
  _log "No clone VM name specified!"
  exit 3
fi

# check if VM name was specified
if [ -z "${sourceVm}" ]; then
  _log "No source VM name specified!"
  exit 4
fi

# check if source VM is defined by libvirt
if ! virsh dominfo "${sourceVm}" > /dev/null 2>&1; then
  _log "VM '${sourceVm}' not defined!"
  exit 5
fi

# check if clone VM is defined by libvirt
if virsh dominfo "${cloneVm}" > /dev/null 2>&1; then
  _log "VM '${cloneVm}' is already defined!"
  exit 6
fi


# create backup xml
tempXml="$(mktemp)"
virsh dumpxml "${sourceVm}" > "${tempXml}"

# change name,uuid,mac address in temp xml file to clone VM name
xmlstarlet edit --inplace --update "/domain/name" -v "${cloneVm}" "${tempXml}"
xmlstarlet edit --inplace --update "/domain/uuid" -v "$(uuid)" "${tempXml}"
sed -i "/mac address/d" "${tempXml}"

# files created so far, removed again if the clone fails
created=()
_fail() {
  local f
  _log "${1}"
  for f in "${created[@]}"; do
    rm -f "${f}"
  done
  rm -f "${tempXml}"
  exit "${2}"
}

# create a linked clone (qcow2 overlay) of every file-backed disk; cdroms and
# block devices are left pointing at their original source
_log "Creating linked clones of each disk ..."
diskXpath="/domain/devices/disk[@device='disk' and source/@file]"
count=$(xmlstarlet sel -t -v "count(${diskXpath})" "${tempXml}")
for (( i=1; i<=count; i++ )); do
  disk="$(xmlstarlet sel -t -v "(${diskXpath})[${i}]/source/@file" "${tempXml}")"
  _log "Source disk :: ${disk}"
  fmt="$(qemu-img info -U "${disk}" 2>/dev/null | sed -n 's/^file format:[[:space:]]*\([[:alnum:]]*\).*/\1/p' | head -n1)"
  if [ -z "${fmt}" ]; then
    _fail "Unable to determine format of ${disk}" 7
  fi
  clonedisk="$(dirname "${disk}")/${cloneVm}_$(basename "${disk}")"
  if [ -e "${clonedisk}" ]; then
    _fail "Clone disk already exists :: ${clonedisk}" 7
  fi
  _log "Clone disk :: ${clonedisk}"
  if ! qemu-img create -f qcow2 -F "${fmt}" -b "${disk}" "${clonedisk}"; then
    _fail "Error creating backing file :: ${clonedisk}" 7
  fi
  created+=("${clonedisk}")
  xmlstarlet edit --inplace --update "(${diskXpath})[${i}]/source/@file" -v "${clonedisk}" "${tempXml}"
  # the overlay is always qcow2, whatever the base format is
  xmlstarlet edit --inplace --update "(${diskXpath})[${i}]/driver/@type" -v "qcow2" "${tempXml}"
done

# give the clone its own copy of the UEFI variable store instead of sharing
# the source VM's file
nvram="$(xmlstarlet sel -t -v "/domain/os/nvram" "${tempXml}")"
if [ -n "${nvram}" ]; then
  clonenvram="$(dirname "${nvram}")/${cloneVm}_VARS.fd"
  if [ -e "${clonenvram}" ]; then
    _fail "Clone NVRAM file already exists :: ${clonenvram}" 8
  fi
  if [ -f "${nvram}" ]; then
    _log "Copying NVRAM :: ${nvram} -> ${clonenvram}"
    if ! cp -a "${nvram}" "${clonenvram}"; then
      _fail "Error copying NVRAM :: ${nvram}" 8
    fi
    created+=("${clonenvram}")
  fi
  xmlstarlet edit --inplace --update "/domain/os/nvram" -v "${clonenvram}" "${tempXml}"
fi

# define clone VM
_log "Creating clone VM :: ${tempXml}"
if ! virsh define --file "${tempXml}"; then
  _fail "Error defining clone VM :: ${cloneVm}" 9
fi

# remove temp file
rm -f "${tempXml}"

_log "Done."

exit 0
