#!/bin/bash
#
# vim:tabstop=3:expandtab:shiftwidth=3
#
# GPLv3 see LICENCE file
#
# $Date: 2026-08-14 00:48:17 +0200 (Fri, 14 Aug 2026) $
# $Revision: 826 $
#
# Helper script to add an extra disk (or disks) to an existing (k)vm
# in qcow2 format.
#

# Store original $* arguments
aRGs="$*"

# Collect VAL opts
vALo="$(grep -v '^#' $BASH_SOURCE | tr '$' '\n' | grep '^{#_[_a-zA-Z][_a-z]\?'|\
        sed 's|^{#\([_a-zA-Z]\+\)[^_a-z].*|\1|' | sort -u)"
nOTy="$(sed -e 's/$/$/' -e 's/\s/$|^/g' -e 's/^/^/' <<< "$(echo $vALo)")"
yESo="$(grep -v '^#' $BASH_SOURCE | tr '$' '\n' | grep '^_[_a-zA-Z][_a-z]\?' |\
        sed 's|^\([_a-zA-Z]\+\)[^_a-z].*|\1|' | grep -E -v "$nOTy" | sort -u)"

# Get the -[-] options into _[_] vars
while [ "${1:0:1}" = '-' ]
do
   opT="$(sed 's|=.*||' <<< "$1")"
   oPT="$(sed 's|-|_|g' <<< "$opT")"
   if grep -q "^${oPT}\$" <<< "$yESo"
   then
      eval "$oPT=yes" 2> /dev/null
   elif grep -q "^${oPT}\$" <<< "$vALo"
   then
      iSvAL="$(grep "^${opT}=" <<< "$1" | sed "s|^${opT}=||")"
      if [ ${#iSvAL} -eq 0 ] && [ $# -gt 0 ]
      then
         shift
         eval "$oPT[\${#${oPT}[@]}]='$1'" 2> /dev/null
      else
         eval "$oPT[\${#${oPT}[@]}]='$iSvAL'" 2> /dev/null
      fi
   fi
   unset opT oPT iSvAL
   shift
done # processing the options, __have_fun

# Help text
if ( [ ${#__remove} -gt 0 ] && [ $# -eq 0 ] ) || \
   ( [ ${#__remove} -eq 0 ] && [ $# -lt 2 ] ) 
then
   echo
   echo "  Usage: `basename $0` KVM-NAME DISK-SIZE [DISK-SIZE] .."
   echo
   echo "   or"
   echo
   echo "  Usage: `basename $0` --remove DISK KVM-NAME"
   echo
   echo "  Options:"
   (sort <<< "$yESo" ; grep -v '^$' <<< "$vALo" |sort|sed 's|$|[=]VALUE|') | \
   grep -v '^___' | sed -e 's|_|-|g' -e 's|^|   |' # hide --- opts + indent
   echo
   
   exit 1
fi

AddOrRmDisk()
{
   if ! virsh list --name --all | grep -v '^$' | grep -q "^$1\$"
   then
      echo "! VM $1 does not exist"
      return 1
   fi

   RootDisk="`virsh domblklist $1 2> /dev/null | \
              grep '^[[:space:]]' | sed 's/^[[:space:]]*//' | \
              grep '^[sv]d[a-z][a-z]*[[:space:]][[:space:]]*' | \
              sort | head -1`"
   RootDiskDev=`echo "$RootDisk" | awk '{print $1}'`
   SystemImage=`echo "$RootDisk" | awk '{print $2}' | \
                sed "s|[.]$RootDiskDev.*||"`
   if [ "$SystemImage" = "" ]
   then
      echo "! Unable to determine system's disk image"
      return 1
   fi

   LastDiskDev="`virsh domblklist $1 2> /dev/null | \
                 grep '^[[:space:]]' | sed 's/^[[:space:]]*//' | \
                 grep '^[sv]d[a-z][a-z]*[[:space:]][[:space:]]*' | \
                 sort | tail -1  | \
                 sed 's,^\([sv]d[a-z][a-z]*\)[[:space:]].*,\1,'`"

   ToRmDiskDev="`virsh domblklist $1 2> /dev/null | grep -v '[-]$' | \
                 grep '^[[:space:]]' | sed 's/^[[:space:]]*//' | \
                 grep '^[sv]d[a-z][a-z]*[[:space:]][[:space:]]*' | \
                 sort | grep -v "^${RootDiskDev}\\s"  | \
                 sed 's,^\([sv]d[a-z][a-z]*\)[[:space:]].*,\1,'`"

   NewDiskDev="${LastDiskDev:0:2}`\
               echo ${LastDiskDev:2:1} | \
               awk '
               {
                  printf("%c\n",index("abcdefghijklmnopqrstuvwxyz",$1)+97);
               }'`"
   # Clean up on overflow
   NewDiskDev="`echo $NewDiskDev | grep '^[sv]d[a-z]$'`"

   if [ ${#__remove} -gt 0 ]
   then
      if ! grep -q "^${__remove}\$" <<< "$ToRmDiskDev"
      then
         echo -n "! Unable to remove ${__remove} (only "
         echo -n $ToRmDiskDev | tr ' ' ',' 
         echo ')'
         return 1
      elif virsh dominfo $1 | grep -q '^State:\s\+running$'
      then
         echo "! Unable to remove ${__remove} ($1 still running)"
         return 1
      else
         RmImage="$(virsh domblklist $1 2> /dev/null | \
                    grep "\\s\\+${__remove}\\s" | \
                    awk '{print $2}')"
         if [ ${#RmImage} -gt 0 ] && [ -f "$RmImage" ]
         then
            if ! virsh detach-disk $1 ${__remove} --persistent --config
            then
               echo "! Unable to detach ${__remove} from $1"
               return 1
            else
               echo "# Detached disk ${__remove} from $1"
               if ! rm -f "$RmImage"
               then
                  echo "! Unable to remove image $RmImage"
                  return 1
               else
                  echo "# Renoved image $RmImage"
               fi
            fi
         fi
      fi
      return 0
   fi

   if [ "$NewDiskDev" = "" ]
   then
      echo "! Unable to determine new disk devicename"
      return 1
   fi

   if [ -f $SystemImage.$NewDiskDev.qcow2 ]
   then
      echo "! Disk image file still exists, disk not added"
      return 1
   fi

   Size=$(echo $2 | sed 's/[0-9]$/\0G/')
   echo "# Creating disk image $SystemImage.$NewDiskDev.qcow2 ($Size)"
   qemu-img create -f qcow2 $SystemImage.$NewDiskDev.qcow2 $Size
   if [ $? -ne 0 ]
   then
      echo "! Creation of disk image $SystemImage.$NewDiskDev.qcow2 failed"
      return 1
   fi

   echo "# Attaching disk image $SystemImage.$NewDiskDev.qcow2 to" \
        "$1 as $NewDiskDev"
   virsh attach-disk $1 $SystemImage.$NewDiskDev.qcow2 \
                        $NewDiskDev \
                        --subdriver qcow2 --persistent
   if [ $? -ne 0 ]
   then
      echo "! Failed to attach disk image $SystemImage.$NewDiskDev.qcow2" \
           "to $1"
      return 1
   fi
}

Kvm=$1
if [ ${#__remove} -gt 0 ]
then
   AddOrRmDisk $Kvm
else
   shift
   while [ "$1" != "" ] && AddOrRmDisk $Kvm $1
   do
      shift
   done
fi
