#! /bin/bash
#########################################################################
#									#
#	lqvm-start is automatically generated,				#
#		please do not modify!					#
#									#
#########################################################################

#########################################################################
#									#
# Author: Copyright (C) 2020-2026  Mark Grant				#
#									#
# Released under the GPLv3 only.					#
# SPDX-License-Identifier: GPL-3.0-only					#
#									#
# Purpose:								#
# A script to start a VM with external, internal or no snapshot.	#
#                                                            	  	#
# Syntax:      See usage()						#
#									#
# Exit codes used:-							#
# Bash standard Exit Codes:	0 - success				#
#				1 - general failure			#
# User-defined exit code range is 64 - 113				#
#	C/C++ Semi-standard exit codes from sysexits.h range is 64 - 78	#
#		EX_USAGE	64	command line usage error	#
#		EX_DATAERR	65	data format error		#
#		EX_NOINPUT	66	cannot open input		#
#		EX_NOUSER	67	addressee unknown		#
#		EX_NOHOST	68	host name unknown		#
#		EX_UNAVAILABLE	69	service unavailable		#
#		EX_SOFTWARE	70	internal software error		#
#		EX_OSERR	71	system error (e.g., can't fork)	#
#		EX_OSFILE	72	critical OS file missing	#
#		EX_CANTCREAT	73	can't create (user) output file	#
#		EX_IOERR	74	input/output error		#
#		EX_TEMPFAIL	75	temp failure; user is invited	#
#					to retry			#
#		EX_PROTOCOL	76	remote error in protocol	#
#		EX_NOPERM	77	permission denied		#
#		EX_CONFIG	78	configuration error		#
#	User-defined (here) exit codes range 79 - 113:			#
#		None							#
#									#
# Further Info:								#
#									#
#########################################################################


set -o pipefail


##################
# Init variables #
##################
readonly version=1.2.1				# Script version
readonly packageversion=1.3.4		# Package version

readonly etclocation=/etc		# Path to config directory

create=false
external=false
internal=false
intextset=false
without=false


#############
# Functions #
#############

# -h --help output.
# No parameters
# No return value
usage()
{
cat << EOF
Usage is:-
$(basename "$0") {-h|-V}
$(basename "$0") -c SNAPSHOT_NAME -D SNAPSHOT_DESC {-e|-i} [-u CONNECTION] VM_NAME
$(basename "$0") [-D SNAPSHOT_DESC] {-e|-i} [-u CONNECTION] VM_NAME
$(basename "$0") -w [-u CONNECTION] VM_NAME
Usage is:-
$(basename "$0") [options]
	-c or --create 'snapshot-name' Create the snapshot using 'snapshot-name'
	-D or --description 'snapshot-description' Plain text description
	-e or --external Create an external snapshot
	-h or --help Display this help information
	-i or --internal Create an internal snapshot
	-u or --connect-uri Use the named connection URI
	-V or --version Print script version information
	-w or --without Do not create a snapshot
EOF
}

# Standard function to emit messages depending on various parameters.
# Parameters -	$1 What:-	The message to emit.
#		$2 Where:-	stdout == 0
#				stderr == 1
# No return value.
output()
{
	if (( !$2 )); then
		printf "%s\n" "$1"
	else
		printf "%s\n" "$1" 1>&2
	fi
}

# Standard function to tidy up and return exit code.
# Parameters - 	$1 is the exit code.
# No return value.
script_exit()
{
	exit "$1"
}

# Standard function to test command error and exit if non-zero.
# Parameters - 	$1 is the exit code, (normally $? from the preceeding command).
# No return value.
std_cmd_err_handler()
{
	if (( $1 )); then
		script_exit "$1"
	fi
}

# Standard trap exit function.
# No parameters.
# No return value.
# shellcheck disable=SC2317  # Don't warn about unreachable commands in this
# function
trap_exit()
{
	local -i exit_code=$?
	local msg

	msg="Script terminating with exit code $exit_code due to trap received."
	output "$msg" 1
	script_exit "$exit_code"
}

# Setup trap
trap trap_exit SIGHUP SIGINT SIGQUIT SIGTERM

# Process the config file just for the parameters of interest.
# Parameters - None
# No return value.
proc_config_file()
{
	local input=()
	local oldIFS=$IFS

	IFS="="

	exec 3<"$etclocation/lqvm.conf"
	std_cmd_err_handler $?
	while read -u3 -ra input; do
		std_cmd_err_handler $?
		case ${input[0]} in
		connecturi)
			connect_uri=${input[1]}
			;;
		bridge)
			bridge=${input[1]}
			;;
		ethernetprefix)
			ethernet_prefix=${input[1]}
			;;
		esac
	done
	exec 3<&-

	IFS=$oldIFS
}

# Process command line arguments with GNU getopt.
# Parameters -	$1 is the command line.
# No return value.
proc_CL()
{
	local GETOPTTEMP
	local msg
	local tmp

	tmp="getopt -o c:D:ehiu:Vw --long create:,description:,external,help"
	tmp+=",internal,connect-uri:,version,without"
	GETOPTTEMP=$($tmp -n "$0" -- "$@")
	std_cmd_err_handler $?

	eval set -- "$GETOPTTEMP"
	std_cmd_err_handler $?

	while true; do
		case "$1" in
		-c|--create)
			create=true
			snapshot_name=$2
			shift 2
			;;
		-D|--description)
			snapshot_description=$2
			shift 2
			;;
		-e|--external)
			if $internal || $without; then
				msg="Options e, i and w are mutually exclusive."
				output "$msg" 1
				script_exit 64
			fi
			external=true
			intextset=true
			shift
			;;
		-h|--help)
			usage
			shift
			script_exit 0
			;;
		-i|--internal)
			if $external || $without; then
				msg="Options e, i and w are mutually exclusive."
				output "$msg" 1
				script_exit 64
			fi
			internal=true
			intextset=true
			shift
			;;
		-u|--connect-uri)
			connect_uri=$2
			shift 2
			;;
		-V|--version)
			printf "%s Script version %s\n" "$0" $version
			printf "%s Package version %s\n" "$0" $packageversion
			shift
			script_exit 0
			;;
		-w|--without)
			if $external || $internal; then
				msg="Options e, i and w are mutually exclusive."
				output "$msg" 1
				script_exit 64
			fi
			without=true
			shift
			;;
		--)	shift
			break
			;;
		*)	output "Internal error." 1
			script_exit 64
			;;
		esac
	done

	# The mandatory and only non-option argument must be the VM name
	if (( $# == 1 )); then
		name=$1
		shift
	else
		output "One argument must be specified, the VM name." 1
		script_exit 64
	fi

	# One option has to be selected.
	if ! $external && ! $internal && ! $without; then
		msg="Either e, i or w must be set."
		output "$msg" 1
		script_exit 64
	fi
	if ( $create || [[ -n $snapshot_description ]]) && $without; then
		msg="Options c and D cannot be used in conjunction with"
		msg+=" option w."
		output "$msg" 1
		script_exit 64
	fi
}

# Validate the VM name.
# Parameters - none
# Return value - none
validate_name()
{
	local -i name_matches

	name_matches=$(virsh -c "$connect_uri" list --all | grep -c "\<$name\>")
	std_cmd_err_handler $?
	if (( ! name_matches )); then
	output "No such VM $name" 1
	script_exit 64
	fi
}

# Determine if the VM is running.
# Parameters	1 - VM.
#		2 - Variable to receive the result.
# Return value - true or false in the variable specified above.
is_vm_running()
{
	local __result=$2
	local tmp_result
	local -i vm_running_matches

	vm_running_matches=$(virsh -c "$connect_uri" list \
		| { grep "\<$1\>" || test $?=1; } | wc -l)
	std_cmd_err_handler $?
	if (( vm_running_matches )); then
		tmp_result="true"
	else
		tmp_result="false"
	fi
	# shellcheck disable=SC2086 # $__result is OK without ""
	eval $__result="'$tmp_result'"
}

# Create the snapshot.
# Parameters - none.
# Return value - none
create_snapshot()
{
	local cmd

	cmd="/usr/bin/lqvm-snap -u $connect_uri"

	if $external; then
		cmd+=" -e"
	else
		cmd+=" -i"
	fi

	if $create; then
		cmd+=" -c \"$snapshot_name\""
	else
		cmd+=" -s"
	fi

	if [[ -n $snapshot_description ]]; then
		cmd+=" -D \"$snapshot_description\""
	fi

	cmd+=" $name"
	eval "$cmd"
	std_cmd_err_handler $?
}

# If a bridge is available use it if the ethernet is hard-wired.
# Bridge and Default are set to opposite values (up / down).
# Parameters - none.
# Return value - none.
process_bridge()
{
	local bridge_mac_addr
	local default_mac_addr

	# Get the mac address of the default.
	default_mac_addr=$(virsh -c "$connect_uri" domiflist "$name" \
		| grep "network   default" \
		| awk '{ print $5 }')
	std_cmd_err_handler $?

	# Get the mac address of the bridge.
	bridge_mac_addr=$(virsh -c "$connect_uri" domiflist "$name" \
		| grep "bridge    \<$bridge\>" \
		| awk '{ print $5 }')
	std_cmd_err_handler $?
	if [[ -z "$bridge_mac_addr" ]]; then
		output "Cannot find mac address." 1
		exit 77
	fi

	# Enable / disable bridge VNI depending on hard-wired ethernet port
	# status.
	if (($(ip link show | grep -c "$ethernet_prefix.*$bridge.*state UP")))
	then
		std_cmd_err_handler $?
		output "Selecting bridge network interface $bridge" 0
		virsh -c "$connect_uri" domif-setlink "$name" \
			"$bridge_mac_addr" up
		std_cmd_err_handler $?
		if [[ -n $default_mac_addr ]]; then
			output "De-selecting non-bridge interface." 0
			virsh -c "$connect_uri" domif-setlink \
				"$name" "$default_mac_addr" down
			std_cmd_err_handler $?
		fi
	else
		output "De-selecting bridge network interface $bridge" 0
		virsh -c "$connect_uri" domif-setlink "$name" \
			"$bridge_mac_addr" down
		std_cmd_err_handler $?
		if [[ -n $default_mac_addr ]]; then
			output "Selecting non-bridge interface." 0
			virsh -c "$connect_uri" domif-setlink "$name" \
				"$default_mac_addr" up
			std_cmd_err_handler $?
		fi
	fi
}


########
# Main #
########

proc_config_file

proc_CL "$@"

validate_name

is_vm_running "$name" vm_running
# shellcheck disable=SC2154 # vm_running is obscurely set in is_vm_running()
if $vm_running; then
	output "VM is already running." 1
	script_exit 77
fi

if [[ -n $bridge ]]; then
	process_bridge
fi

if $intextset; then
	create_snapshot
fi

output "Starting VM $name" 0
virsh -c "$connect_uri" start "$name"
std_cmd_err_handler $?

# And exit.
script_exit 0

