summaryrefslogblamecommitdiffstats
path: root/core/modules/run-virt/data/opt/openslx/vmchooser/run-virt-includes/vmchooser_runvirt_functions.inc
blob: 08ab4f72d245da5ec97b3d3295db1097d546983e (plain) (tree)
1
2
3
4
           


                                                       






















                                                                                                                                     
 

                                                      
            
                                               



                                       
                                   

                       




                                                                    

 

                                                                


                        
                                           


                                           

                                                     
              
                                       
             






                                           
            

                             
                             

                                               




                                                        
                      

                                                                 


                                                
                                                                   



                                                




























                                                                                                                             
                                                




                                                                          
                                                                               
             
                                          
                                                                                 
                     










                                                                                                               
                     
 


                                                  
                                                                
                                            


                                     
                                                            

                                  

                                                                                 
 








                                                                                                                                 
                                                                                                                                                      








                                                                             
          
                                     

                                                      
            




                                                                     






                                     

                                                   

 





























                                                                                                      






                                    
                                         



















                                                                   
                                                      
                                                      











                                                                                         
                                                                                














                                                                                                                                 
                                                                                                        








                                                                                          
                





















































                                                                                                    
          







                                                                                                

 
















                                                                 

















                                                     















                                                                       

 

                                                          















                                                                                                         
                                                                                   




                                                      
                                                             






                                                                              
                                                                                                    






                                                                     
                                                                                 



























                                                                                
                                           

 














                                                                      
 















                                                                                  


















                                                                
#!/bin/bash
#######################################################
# Include: Set functions needed by vmchooser-run_virt #
#######################################################
## Assigns an ID to the currently starting VM to support
# multiple instances running simultaneously.
# Note that VM_ID will always have two digits.
get_vm_id() {
	local script=${BASH_SOURCE[-1]}
	[ -z "$script" ] && script="$0"
	if [ -n "$script" ]; then
		script=$(readlink -f "$script")
		if [ -n "$script" ] && [ -s "$script" ]; then
			#bingo
			declare -g VM_ID=$(ps ax | grep -F "$script" | grep -v 'grep' | grep -o -- "${script}.*\$" | sort -u | wc -l)
			if [ "$VM_ID" -gt 0 ]; then
				[ "${#VM_ID}" -eq 1 ] && VM_ID="0${VM_ID}"
				[ "${#VM_ID}" -gt 2 ] && VM_ID="${VM_ID:0:2}"
				[ "${#VM_ID}" -eq 2 ] && readonly VM_ID && return
			fi
		fi
	fi
	# fallback: take last two digits of current pid...
	VM_ID=$(expr substr $$ $(expr ${#$} - 1) 2)
	[ "${#VM_ID}" -eq 1 ] && VM_ID="0${VM_ID}"
	readonly VM_ID
}

################# LOGGING FUNCTIONS ##################
# Helper function to write to stdout and logfile
writelog() {
	local DATE="$(date +%Y-%m-%d-%H-%M-%S)"
	# write to stdout?
	if [ "x$1" = "x--quiet" ]; then
		shift
	else
		echo -e "$DATE: $*"
	fi
	# log into file
	if $DEBUG; then
		echo -e "$DATE: (${FUNCNAME[1]}) $*" >> "${LOGFILE}"
	else
		echo -e "$DATE: $*" >> "${LOGFILE}"
	fi
}

# Helper function to notify the user.
# This directly returns and do not wait for a user confirmation.
notify_user() {
	local TOPIC="$1"
	shift
	notify-send -u normal "$TOPIC" "$*"
	writelog "Notify: **${TOPIC}**: $*"
}

# Helper to display an error message box to the user.
# Only returns when the user dismisses the message.
error_user() {
	local TITLE="$(translate "$1")"
	shift
	local BODY="$*"
	local MSG
	if [ -n "$BODY" ]; then
		BODY="$(translate "$BODY")"
		MSG="   $TITLE

$BODY"
	else
		MSG="$TITLE"
		BODY="$TITLE"
		TITLE="ERROR"
	fi
	# Zenity should yield the nicest result
	# TODO the title is only set as the window name,
	# which cannot be seen without a window manager
	zenity --error --title "$TITLE" --text "$BODY"
	local RET=$?
	[ $RET -le 1 ] && return
	# no zenity...
	# QnD abuse printergui for error message as it's blocking
	/opt/openslx/cups/printergui --error "$MSG" && return
	# no printergui...
	# xmessage is ugly but gets the job done
	xmessage "$MSG" && return
	# unfortunately, I can only think of notify+sleep right now
	notify-send -u critical "$TITLE" "$BODY"
	sleep 10
}

################## CLEANUP FUNCTIONS ##################
# Registers functions to be called when cleanexit is called.
# Only accepts functions that were not previously registered.
# This kinda detects when a cleanup function was overriden,
# or at least that something is fishy.
declare -ag CLEANUP_TASKS
add_cleanup() {
	[ $# -lt 1 ] && writelog "'${FUNCNAME[0]}' needs at least one argument! $# given." && return
	# check if the given function name is already used
	while [ $# -ne 0 ]; do
		if array_contains CLEANUP_TASKS "$1"; then
			writelog "Cleanup function '$1' already registered! Are there multiple definitions of this function?"
			writelog "This might suggest that a previously defined cleanup function was overriden!"
			return 1
		fi
		CLEANUP_TASKS+=("$1")
		shift
	done
	return 0
}
# This function will be called at the end of vmchooser-run_virt
# or upon critical errors during the runtime.
# It will call the cleanup functions registered through 'add_cleanup'
# and clean TMPDIR if appropriate. Further, it will evaluate and
# process the EXIT_{TYPE,REASON} variables that hold information
# on why we are about to exit.
#
# EXIT_TYPE should be either:
#	- 'internal' for critical internal errors, this will
#     automatically send the logfile via slxlog.
#	- 'user' for errors related to the user's choice
# Others could be thought of like 'external' for failures
# with remote services (e.g. slx apis, external image repo, ...)
#
# EXIT_REASON should contain a user-friendly message to print to the user.
# it can be prefixed with err.\S+, which will serve as a translation identifier
cleanexit() {
	trap "" SIGHUP SIGINT SIGTERM EXIT
	writelog "Cleanexit '$1' triggered by '${BASH_SOURCE[1]}:${FUNCNAME[1]}'"
	usleep 250000
	while isset CLEANUP_TASKS; do
		local TASK=${CLEANUP_TASKS[-1]}
		unset -v CLEANUP_TASKS[-1]
		if ! is_function $TASK; then
			writelog "Registered cleanup function '$TASK' is not a function. This should not be..."
			continue
		fi
		if ! ${TASK}; then
			writelog "Failed to run cleanup function '$TASK'! Exit code: $RET"
		fi
	done
	usleep 250000

	# kill potential remaining background jobs
	kill $(jobs -p)

	# If we're not in debug mode, remove all temporary files
	if ! $DEBUG && notempty TMPDIR; then
		rm -rf -- "${TMPDIR}"
	fi

	# Now see if we need to do the catch all error stuff
	# if 0 given, exit 0
	[ "x$1" = "x0" ] && exit 0
	# if no code was given, exit 129
	[ $# -eq 0 ] && writelog "Cleanexit called without arguments! Dev error?"

	# given exit code is set and not 0, handle the error now
	# now evaluate the EXIT_{TYPE,REASON} variables and generate error title/text
	local ERR_TITLE ERR_TEXT ERR_FOOTER
	# default error footer
	ERR_FOOTER="Versuchen Sie den Computer neuzustarten und falls das Problem bestehen bleibt, kontaktieren Sie den Support."
	if notempty EXIT_TYPE; then
		case "${EXIT_TYPE}" in
			user)
				ERR_TITLE="Auswahlfehler"
				ERR_FOOTER="Beim Start Ihrer Veranstaltung sind Fehler aufgetreten. Versuchen Sie es mit einer anderen Veranstaltung."
				;;
			internal)
				ERR_TITLE="Interner Fehler"
				;;
			*)
				ERR_TITLE="Unbekannter Fehler"
				writelog "Unknown EXIT_TYPE: '${EXIT_TYPE}'."
				;;
		esac
	fi
	if notempty EXIT_REASON; then
		# Try i18n
		ERR_TEXT="$(translate "$EXIT_REASON")"
	else
		# this should never happen if EXIT_REASON is properly
		# used when calling cleanexit !
		ERR_TEXT="Unbekannter Fehler"
	fi

	# finally display the error
	error_user "${ERR_TITLE}" "
${ERR_TEXT}

${ERR_FOOTER}
"
	writelog "All done. Exiting."
	# if no exit code was given as $1, exit 129
	exit "${1:-129}"
}

##
# Translate the given string using a lookup file for messages/strings
# This expects one parameter in the form of "msg.foo.bar_baz${IFS}Fallback msg"
# where the first word is really just of the format msg.*
# If no translation is found, the rest of the string will be returned. If the
# argument does not start with msg.*, the whole string will be returned as is
translate() {
	local ERR_TEXT_CODE
	read -r ERR_TEXT_CODE _ <<<$1 # No quotes to strip leading spaces automatically
	if [[ "$ERR_TEXT_CODE" = msg.* ]]; then
		# Message starts with message identifier
		if ! is_array TRANSLATION_STRING; then
			unset TRANSLATION_STRING
			if [ -s "${RUN_VIRT_INCLUDE_DIR}/strings.inc" ]; then
				declare -A TRANSLATION_STRING
				$(safesource "${RUN_VIRT_INCLUDE_DIR}/strings.inc")
			fi
		fi
		if is_array TRANSLATION_STRING && [ -n "${TRANSLATION_STRING[$ERR_TEXT_CODE]}" ]; then
			echo "${TRANSLATION_STRING[$ERR_TEXT_CODE]}"
			return 0
		fi
		# Identifier not found in translation file, fallback
		echo "${1#*$ERR_TEXT_CODE}"
	else
		# No identifier, output everything
		echo "$1"
	fi
}

##
# run_hooks type args...
# eg run_hooks "download" "$CONFDIR"
# returns 100 if no hooks exist
run_hooks() {
	local dir file retval r
	declare -a files
	dir="$VMCHOOSER_DIR/hooks/${1}.d"
	[ -d "$dir" ] || return 100
	shift
	files=( "${dir}"/* )
	retval=100
	for file in "${files[@]}"; do
		[ -e "${file}" ] || continue
		r=100
		if [ "${file##*.}" = "sh" ] && [ -x "$file" ]; then
			export TMPDIR IMGUUID USER
			"$file" "$@"
			r="$?"
		elif [ "${file##.*}" = "inc" ]; then
			. "$file"
			r="$?"
		fi
		[ "$r" -lt "$retval" ] && retval="$r"
	done
	return "$retval"
}

################# SOURCING FUNCTIONS #################
# Wrapped 'source' that first checks for existence and
# syntax before actually sourcing the given file.
# The option '--exit' triggers cleanexit upon syntax errors.
# Without it, it returns 0 when tests passed, 1 otherwise.
# Usage:
#	safesource [--exit] <files>
safesource() {
	declare -i EXIT_ON_FAILURE=0
	[ "x$1" = "x--exit" ] && EXIT_ON_FAILURE=1 && shift
	while [ $# -gt 0 ]; do
		# sanitze filename just to be sure as it is part of the eval coming later
		# alphanumeric and - _ . should be enough for common file naming scheme
		if [[ ! "$1" =~ ^[a-zA-Z0-9./_-]+$ ]]; then
			writelog "'$1' is a weird filename to source! Ignoring."
			return 1
		fi
		local FILE="$1"
		shift
		bash -n "${FILE}"
		local -i RET=$?
		if [ $RET -ne 0 ]; then
			case $RET in
				1) writelog --quiet "Bad file to source: ${FILE}" ;;
				2) writelog --quiet "Bad syntax: ${FILE}" ;;
				126) writelog --quiet "Could not access: ${FILE}" ;;
				127) writelog --quiet "File not found: ${FILE}" ;;
				*) writelog --quiet "Syntax check (bash -n) returned unknown error code '${RET}' for: ${FILE}" ;;
			esac
			if [ $EXIT_ON_FAILURE -eq 1 ]; then
				echo "eval EXIT_REASON=\"Could not safesource '${FILE}'\" cleanexit 1 ;"
			else
				echo "eval writelog \"Could not safesource '${FILE}'.\" ;"
			fi
			return 1
		fi
		echo "eval source ${FILE} ;"
		echo "run_post_source ${FILE} ;"

	done
	return 0
}

# Registers functions to be called after sourcing an include.
# Includes should only define functions and register them
# to be called after successfully sourcing.
declare -Ag RUN_POST_SOURCE
call_post_source() {
	while [ $# -gt 0 ]; do
		if ! is_function "$1"; then
			writelog "Tried to register a non-function: '$1'"
			continue
		fi
		if notempty BASH_SOURCE[1]; then
			RUN_POST_SOURCE[${BASH_SOURCE[1]}]+="$1 "
			shift
		else
			writelog "Could not determine the sourced file calling ${FUNCNAME[0]}"
		fi
	done
}
# Helper called after sourcing the file via safesource. It just calls the
# functions in the same order they were registered.
run_post_source() {
	[ $# -ne 1 ] && writelog "'${FUNCNAME[0]}' expects one argument only! $# given." && return 1
	for TASK in ${RUN_POST_SOURCE["${1}"]}; do
		# sanity checks
		if ! is_function $TASK; then
			writelog "\tRegistered function '$TASK' is not a function!"
			return 1 # TODO maybe even cleanexit here as this seems very bad...
		fi
		# remove from stack before running it
		RUN_POST_SOURCE["${1}"]="${RUN_POST_SOURCE[${1//${TASK}\ /\ }]}"
		${TASK}
		local -i RET=$?
		if [ $RET -ne 0 ]; then
			writelog "\tFailed to run post source '${TASK}' (Exit code: $RET)"
			return $RET
		fi
	done
	return 0
}

################# FEATURE FUNCTIONS ##################
# Helper to register feature handlers, read run-virt.d/README
declare -Ag FEATURE_HANDLERS
reg_feature_handler() {
	if [ $# -ne 2 ]; then
		writelog "'${FUNCNAME[0]}' expects 2 arguments! $# given."
		return 1
	fi
	if notempty FEATURE_HANDLERS["$1"]; then
		writelog "'${BASH_SOURCE[1]}' tried to overwrite feat handler '$1'! Ignoring."
		# maybe allow overwritting?
		return 1
	fi
	if ! is_function "$2"; then
		writelog "'${BASH_SOURCE[1]}' tried to register a non-function as feat handler!"
		writelog "\t'$2' is a '$(type -t $2 2>&1)'."
		return 1
	fi
	# all good, save it
	FEATURE_HANDLERS["$1"]="$2"
	return 0
}


################### XML FUNCTIONS ####################
# Extract given xpath from given xml file
# e.g.: xmlextract '//node/nestednode/@attribute' "$file"
# @param
# @return Plain text, UTF-8
xmlextract() {
	xmlstarlet sel -T -E utf-8 -t -v "$1" "$2"
}

# Wrapper for convenience
get_xml () {
	xmlextract "//settings/eintrag/${1}/@param" "${XML_FILE}"
}


################## HELPER FUNCTIONS ##################
# Check if the given variables are set (empty or not)
isset() {
	while [ $# -gt 0 ]; do
		[ -z "${!1+x}" ] && return 1
		shift
	done
	return 0
}

# Check if the given variables are not empty
notempty() {
	while [ $# -gt 0 ]; do
		[ -z "${!1}" ] && return 1
		shift
	done
	return 0
}

# Convenience function
isempty() {
	! notempty $@
}

# Helper to test if given arguments are declared as functions
is_function() {
	while [ $# -gt 0 ]; do
		local TYPE="$(type -t "$1" 2>/dev/null)"
		if [ "x${TYPE}" != "xfunction" ]; then
			writelog "'$1' not a function but a '${TYPE}'."
			return 1
		fi
		shift
	done
	return 0
}

# Helper to test if given arguments are declared as arrays
is_array() {
	# -ne 1 ] && writelog "is_array: Expects 1 argument! $# given." && return 1
	while [ $# -gt 0 ]; do
		local ARRAY_DEF="$(declare -p ${1} 2>/dev/null)"
		if [[ ! "${ARRAY_DEF}" =~ "declare -a" ]] && [[ ! "${ARRAY_DEF}" =~ "declare -A" ]]; then
			return 1
		fi
		shift
	done
	return 0
}

# Helper to test is the given array contains given value
# Usage:
#	array_contains ARRAY_TO_TEST <values...>
array_contains() {
	if [ $# -lt 2 ]; then
		#writelog "${FUNCNAME[0]}: Expects at least 2 arguments, $# given."
		return 1
	fi
	# is $1 even defined?
	local ARRAY_DEF="$(declare -p $1 2>/dev/null)"
	if isempty ARRAY_DEF; then
		#writelog "${FUNCNAME[0]}: '$1' not defined!"
		return 1
	fi
	local ARRAY_NAME="$1"
	shift

	# sanity check on $ARRAY_DEF being either indexed or associative array
	if ! is_array "${ARRAY_NAME}"; then
		#writelog "${FUNCNAME[0]}: '${ARRAY_NAME}' not an array! Declared as:\t${ARRAY_DEF}"
		return 1
	fi

	# now test if array contains the given values in $2+
	while [ $# -gt 0 ]; do
		# check if ARRAY_DEF contains '"<value>"'
		if [[ ! "${ARRAY_DEF}" =~ '="'${1}'"'[^\]]+ ]]; then 
			#writelog "${FUNCNAME[0]}: '${1}' not in '${ARRAY_NAME}'"
			return 1
		fi
		shift
	done
	return 0
}

# Helper to check if the given arguments are valid command names.
# This uses 'type -t' thus supports notably binaries, functions
# and aliases (might need these one day).
# By default, only 0 is returned if all arguments are found.
# Use '--oneof' to return 0 if any one of the arguments are found.
check_dep() {
	[ $# -lt 1 ] && return 1
	unset ONEOF
	if [ "x$1" = "x--oneof" ]; then
		local ONEOF="1"
		shift
	fi
	while [ $# -gt 0 ]; do
		if ! type -t "$1" >/dev/null 2>&1 ; then
			writelog "Dependency check failed! Could not find '$1'."
			isset ONEOF || return 1
		else
			isset ONEOF && return 0
		fi
		shift
	done
	isset ONEOF && return 1 || return 0
}

# TODO: This is only used once in the whole script:
# to cleanup the os string stored in the xml
# Since the rework of this script, the os strings come from
# the satellite server which already gives us a sanitized string
# thus this function might not be needed anymore, as calling it on
# new gen os strings effectively does nothing.
# Removes any non-alphanumerical and non-hyphen chars
# from the given parameters.
clean_string() {
	if [ "$#" -ge 1 ]; then
		echo "$@" | tr '[A-Z]' '[a-z]' | tr -d -c '[a-z0-9\-]'
	else
		tr '[A-Z]' '[a-z]' | tr -d -c '[a-z0-9\-]'
	fi
}

# Helper to detect given cpu flags.
# If more than one flag is given, assume that matching
# any of them is sufficient.
# Returns 0 if detected, 1 otherwise.
detect_cpu_flag() {
	if [ "$#" -eq 0 ]; then
		writelog "${FUNCNAME[0]} requires at least one argument, 0 given."
		return 1
	fi
	local flags=$1
	while [ "$#" -ne 0 ]; do
		flags="$flags|$1"
		shift
	done
	grep -m1 -qE '^flags\s*:.*\b('"${flags}"')\b' /proc/cpuinfo
}

# downloads the given URL to given file
download_file() {
  [ $# -ne 2 ] && writelog "Usage: $0 <url> <path>." && return 1
  local _url="$1"
  local _path="$2"
  echo "Downloading '$_url' to '$_path'..."
  if ! wget -T 6 -O "$_path" "$_url" 2> /dev/null >&2; then
    writelog "Downloading '$_url' failed."
    return 1
  fi
  if [ ! -s "$_path" ]; then
    # zero bytes, log and ignore
    writelog "Downloaded resource from '$_url' has zero bytes."
    return 1
  fi
  return 0
}