#!/usr/bin/bash
# create and run clone

set -eu

show_help() {
    cat << EOF >&2
Usage: $(basename "$0") [options] vmname"
Create a new clone, start the vm (if not yet running) and run virt-viewer.
Squid-Proxy will be started too.
options:
    -n|--new             new clone will be created, even if exists
    -p|--persistent      new clone will be created persistent, so available after reboot too
    -s|--system          qemu:///system instead of default qemu:///session
       --no-viewer       start without viewer
       --heads n         number of displays
       --memory sizeMB   memory size in MB
       --cpu num         number of CPUs
       --os OS           operating system (win10|linux|..)
       --data-disk size  additional data-disk
       --bridge virbrX   additional network interface on bridge virbrX
       --uid uid         set uid on guest
       --gid gid         set gid on guest
       --macvtap         additional network interface on device macvtap
       --options options additional options for virt-install command
EOF
}

exit_script() {
    echo "run-vm.sh ${VM_NAME} terminated by trap!" >> "/tmp/${UID}/exit-run-vm.log"
    virsh --connect="${QEMU}" destroy "${VM_NAME}-clone"
    trap - SIGHUP SIGINT SIGTERM # clear the trap
    kill -- -$$ # Sends SIGTERM to child/sub processes
}

check_images() {
   # sync vm-torrent and TPM data
   sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${VM_NAME}.qcow2.torrent" "${VM_NAME}.permall"
   [[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${VM_NAME}.qcow2"

   BACKINGARRAY=()
   imgfile="${VM_SYSDIR}/${VM_NAME}.qcow2" && [[ -f "${VM_DIR}/${VM_NAME}.qcow2" ]] && imgfile="${VM_DIR}/${VM_NAME}.qcow2"
   BACKINGARRAY+=("${imgfile}")
   echo "Imgfile=$imgfile"
   if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
       if [[ ! -f "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent" ]]; then
           echo "no base VM disk '${VM_NAME}.qcow2' found and/or ${VM_NAME} not found on server" >&2
           exit 1
       fi
       # sync vm-disk image by torrent
       echo "Try to sync VM ${VM_NAME} by torrent"
       sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${VM_NAME}"
   fi

   backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
   while [[ -n "${backingfile}" ]]; do
       echo "Backingfile required: ${backingfile}"
       imgfile="${VM_SYSDIR}/${backingfile}" && [[ -f "${VM_DIR}/${backingfile}" ]] && imgfile="${VM_DIR}/${backingfile}"
       BACKINGARRAY+=("${imgfile}")
       sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${backingfile}.torrent"
       [[ -f "${VM_SYSDIR}/${backingfile}" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${backingfile}"
       if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
           # sync vm-disk image by torrent
           echo "Try to sync backingfile ${backingfile} by torrent"
           sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${backingfile%.qcow2}"
       fi
       backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
   done

   echo "VM-Image and required backingfiles available"
   echo "Now, let's check the images."

   # Check VM-Images in reverse order
   for ((i=${#BACKINGARRAY[@]}-1; i>=0; i--))
   do
       echo "Checking ${BACKINGARRAY[$i]}"
       if ! qemu-img check -U "${BACKINGARRAY[$i]}" 2>/dev/null; then
           echo "check failed!"
           echo "sync ${BACKINGARRAY[$i]} again"
           sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${BACKINGARRAY[$i]}.torrent"
           sudo -u lmnsynci /usr/local/bin/vm-sync get_image "$(basename "${BACKINGARRAY[$i]}" .qcow2)"
       fi
   done
   echo "VM-Image and required backingfiles available and checked"

   sudo -u lmnsynci /usr/local/bin/vm-sync update_usage_information ${BACKINGARRAY[*]}
}

create_clone() {
    local VM_NAME="$1"

    if ! [[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" || -f "${VM_DIR}/${VM_NAME}.qcow2" ]]; then
	echo "qcow2 File does not exists." >&2
	exit 1
    fi

    # Create User-VM-Dir and link system VM-Images
    [[ -d "${VM_DIR}" ]] || mkdir -p "${VM_DIR}"
    if [[ "${PERSISTENT}" -eq 1 ]]; then
        sudo /usr/local/bin/vm-link-images -p
    else
        sudo /usr/local/bin/vm-link-images
    fi

    # Create backing file
    cd "${VM_DIR}"
    qemu-img create -f qcow2 -F qcow2 -b "${VM_NAME}.qcow2" "${VM_NAME}-clone.qcow2"

    if [[ -f "${VM_SYSDIR}/${VM_NAME}.permall" ]]; then
      # Copy tpm file
      if [[ ! -f "${VM_NAME}.permall" ]]; then
        echo "copy tpm-file"
        cp "${VM_SYSDIR}/${VM_NAME}.permall" .
      fi
      # create tpm-clone file
      echo "create tpm-clone-file"
      cp "${VM_NAME}.permall" "${VM_NAME}-clone.permall"
    fi

}

create_printerlist() {
  ## Prepare .printerlist.csv
  mkdir -p "${VM_MEDIADIR}"
  chgrp "$(id -g)" "${VM_MEDIADIR}"
  echo "Name;IppURL" > "${VM_MEDIADIR}/.printerlist.csv"
  for p in $(lpstat -v | cut -f 3 -d" " | sed 's/:$//'); do
    echo "$p;ipp://192.168.122.1/printers/$p" >> "${VM_MEDIADIR}/.printerlist.csv"
  done
}

create_mountlist() {
  if id | grep -q teachers; then
    NETHOME=/srv/samba/schools/default-school/teachers/$USER
  else
    NETHOME=(/srv/samba/schools/default-school/students/*/"$USER")
  fi
  NETHOME="${NETHOME#/srv/samba/schools}"
  cat << EOF > "/lmn/media/${USER}/.mounts.csv"
Drive;Remotepath
H;\\\\10.190.1.1${NETHOME//\//\\}
T;\\\\10.190.1.1\default-school\share
EOF
  echo "${USER}" > "/lmn/media/${USER}/.user"
}

start_virtiofsd() {
  # BEGIN temporary fix, while linux-starter are not migrated to --uid and --gid
  if [[ "$LIBVIRTOSINFO" =~ debian.* ]]; then
    [[ "$GUEST_UID" == 0 ]] && GUEST_UID=1010
    [[ "$GUEST_GID" == 0 ]] && GUEST_GID=1010
  fi
  # END temporary fix
  socket="/run/user/$(id -u $USER)/virtiofs-${VM_NAME}.sock"
  systemd-run --user /usr/local/bin/virtiofsd --uid-map=:${GUEST_UID}:${UID}:1: --gid-map=:${GUEST_GID}:$(id -g):1: \
	  --socket-path "$socket" --shared-dir "/lmn/media/${USER}" --syslog
}

ask_really_persistent() {
  cat << EOF >&2

!!!!!!!!!!!!!!! Wichtig !!!!!!!!!!!!!!

Auf dem Computer existiert noch keine persistente VM mit dem Namen ${VM_NAME}.
Das Anlegen persistenter Maschinen sollte nur auf Computern geschehen,
die dem jeweiligen Benutzer zugeordnet sind.
In Klassenzimmern oder Computerräumen ist das Verwenden persistenter
Maschinen normalerweise nicht sinnvoll und sprengt die verfügbaren
Festplattenkapazität.

EOF
  read -rp "Ist die Installation einer persistenten VM wirklich gewünscht? ja/nein " answer
  if [[ "${answer,,}" ==  "ja" ]]; then
    VM_DIR="${VM_DIR_PERSISTENT}"
    echo "Die VM ${VM_NAME} wird persistent auf der Festplatte angelegt!"
    sleep 5
  else
    PERSISTENT=0;
    VM_DIR="${VM_DIR_CONF}"
    echo "Die VM ${VM_NAME} wird nicht persistent gestartet!"
    sleep 5
  fi
}

QEMU='qemu:///session'

NEWCLONE=0
PERSISTENT=0
LIBVIRTOSINFO="win10"
LIBVIRTOPTS=""
NO_VIEWER=0
GUEST_UID=0
GUEST_GID=0

source /etc/lmn/vm.conf
VM_DIR_CONF="${VM_DIR}"

TEMP=$(getopt -o no:ps --long new,no-viewer,options:,persistent,system,memory:,data-disk:,heads:,cpu:,bridge:,macvtap,os:,uid:,gid:,help -n $0 -- "$@")
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

eval set -- "$TEMP"

while true; do
  case "$1" in
    -p | --persistent )
	    PERSISTENT=1;
	    VM_DIR="${VM_DIR_PERSISTENT}"
	    shift
	    ;;
    -n | --new )
            NEWCLONE=1
	    shift
	    ;;
    -s | --system )
            QEMU='qemu:///system'
	    shift
	    ;;
    -o | --options )
            LIBVIRTOPTS=$2
	    shift 2
	    ;;
    --no-viewer )
            NO_VIEWER=1
	    shift
	    ;;
    --data-disk )
            LIBVIRTOPTS="${LIBVIRTOPTS}  --disk ${VM_DIR}/data.qcow2,size=$2,sparse=yes"
	    shift 2
	    ;;
    --heads )
            for i in $(seq $2)
            do
              LIBVIRTOPTS="${LIBVIRTOPTS}  --video model.heads=$i"
            done
            shift 2
            ;;
    --memory )
	    mem_avail=$(sed -En "s/^MemAvailable:\s+([0-9]+)\s+kB/\1/p" /proc/meminfo)
	    mem_avail=$((mem_avail / 1024 - 2048))
	    if (( $2 < mem_avail )); then
	      LIBVIRTOPTS="${LIBVIRTOPTS} --memory $2"
	    else
	      LIBVIRTOPTS="${LIBVIRTOPTS} --memory ${mem_avail}"
	    fi
	    shift 2
	    ;;
    --cpu )
	    #cpu=$(sed -En "0,/^cpu cores/s/^cpu cores\s+:\s+([0-9]+)/\1/p" /proc/cpuinfo)
	    cpu=$(lscpu | grep "^CPU(s):" | sed 's/.* //g')
	    if (( $2 < cpu )); then
	      LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu $2"
	    else
	      LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu ${cpu}"
	    fi
	    shift 2
	    ;;
    --bridge )
            if ip link | grep $2; then
               LIBVIRTOPTS="${LIBVIRTOPTS} --network=bridge=$2,model.type=virtio"
            fi
	    shift 2
	    ;;
    --macvtap )
	    for interface in $(ip link | sed -En 's/.*(macvtap-.*)@.*/\1/p'); do
              mac="$(ip link | grep -A1 "${interface}" | \
                sed -nE "s%\s+link/ether ([[:xdigit:]:]{17}) .+%\1%p")"
              type="ethernet,mac=${mac},target.dev=${interface},xpath1.set=./target/@managed=no,model.type=virtio"
	      LIBVIRTOPTS="${LIBVIRTOPTS} --network type=$type"
	    done
	    shift
	    ;;
    --os )
            LIBVIRTOSINFO=$2
	    shift 2
	    ;;
    --uid )
            GUEST_UID=$2
	    shift 2
	    ;;
    --gid )
            GUEST_GID=$2
	    shift 2
	    ;;
    --help )
            show_help
            exit 1
            ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

# if less than one arguments supplied, display usage
if [[  $# -ne 1 ]] ; then
    show_help
    exit 1
fi

VM_NAME=$1

systemctl --user restart usersquid.service &

# check, if persistent VM is really wanted
if [[ "${PERSISTENT}" == 1 ]] && [[ ! -f "${VM_DIR_PERSISTENT}/${VM_NAME}.qcow2" ]]; then
    ask_really_persistent
fi

# because virsh has problems with long pathnames, using diffent configdir
export XDG_CONFIG_HOME="/var/tmp/vm/${UID}"

if  ! virsh --connect="${QEMU}" list | grep "${VM_NAME}-clone"; then
    echo "VM not yet running."
    # only when school-network is reachable
    if nslookup "${SEEDBOX_HOST}"; then
      check_images
    fi
    if [[ "${NEWCLONE}" = 1 ]] || [[ ! -f "${VM_DIR}/${VM_NAME}-clone.qcow2" ]]; then
        create_clone "${VM_NAME}"
    fi
    # delete the old vm
    virsh --connect=qemu:///session undefine --nvram "${VM_NAME}-clone" || echo "${VM_NAME}-clone did not exist"
    #trap exit_script SIGHUP SIGINT SIGTERM

    create_printerlist
    create_mountlist

    # start virtiofsd-service
    [[ "${QEMU}" = 'qemu:///session' ]] && start_virtiofsd

    uuid=$(openssl rand -hex 16)
    uuid="${uuid:0:8}-${uuid:8:4}-${uuid:12:4}-${uuid:16:4}-${uuid:20:12}"

    if [[ -f "${VM_DIR}/${VM_NAME}-clone.permall" ]]; then
      mkdir -p "/var/tmp/vm/${UID}/.config/libvirt/qemu/swtpm/${uuid}/tpm2/"
      ln "${VM_DIR}/${VM_NAME}-clone.permall" "/var/tmp/vm/${UID}/.config/libvirt/qemu/swtpm/${uuid}/tpm2/tpm2-00.permall"
      LIBVIRTOPTS="${LIBVIRTOPTS} --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb "
    fi

    # finally, create the new vm

    virt-install \
	     --uuid="${uuid}" \
             --osinfo "${LIBVIRTOSINFO}" \
	     --name "${VM_NAME}-clone" \
	     --import \
             --clock hpet_present=yes,hypervclock_present=yes \
             --features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
	     --memorybacking source.type=memfd,access.mode=shared \
	     --disk "${VM_DIR}/${VM_NAME}-clone.qcow2",driver.discard=unmap,target.bus=scsi,cache=writeback \
	     --network=bridge=virbr0,model.type=virtio \
             --filesystem driver.type=virtiofs,accessmode=passthrough,target.dir=virtiofs,xpath1.set=./source/@socket="/run/user/${UID}/virtiofs-${VM_NAME}.sock" \
	     --controller type=scsi,model=virtio-scsi \
	     --check path_in_use=off \
             --connect="${QEMU}" \
	     --noautoconsole \
	     ${LIBVIRTOPTS}

#	     --dry-run \
#             --print-xml \
#	     > /tmp/vm.xml
#	     --features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
#	     --network type=ethernet,target.dev=vm-macvtap,xpath1.set=./target/@managed=no \
#    virsh --connect="${QEMU}" start "${VM_NAME}-clone"
fi
if [[ $NO_VIEWER == 0 ]] ; then
  echo "starting viewer"
  trap exit_script SIGHUP SIGINT SIGTERM
  virt-viewer --connect="${QEMU}" --full-screen "${VM_NAME}-clone"
fi