# ConfigDrive metadata source selection
# vim:set filetype=sh:
# shellcheck shell=sh

# Return 1 for an absent/unusable drive, 2 for a failed load.
configdrive_load() (
	local provider="$1" version="$3" directory="$4"
	local mntdir stagedir mounted format data metadata value loaded rv

	findfs LABEL=config-2 >/dev/null 2>&1 ||
		findfs LABEL=CONFIG-2 >/dev/null 2>&1 || return 1

	. "$LIBDIR/tiny-cloud/metadata-volume"
	mntdir=$(mktemp -d "$TINY_CLOUD_RUN/metadata-volume.XXXXXX") || return 2
	trap '
		rv=$?
		[ -z "$stagedir" ] || rm -rf "$stagedir"
		if [ -n "$mounted" ]; then
			metadata_volume_umount "$mntdir" || rv=2
		else
			rmdir "$mntdir" 2>/dev/null || :
		fi
		exit "$rv"
	' 0
	trap 'exit 2' HUP INT TERM
	metadata_volume_mount "$mntdir" "config-2 CONFIG-2" "vfat iso9660" || return 1
	mounted=1

	for format in native ec2; do
		case "$2" in
			auto|"$format") ;;
			*) continue;;
		esac
		case "$version" in ''|.*|*[!A-Za-z0-9_.-]*) continue;; esac
		case "$format" in
			native)
				data="$mntdir/openstack/$version"
				metadata=meta_data.json
				value=$(yx -f "$data/$metadata" -- uuid 2>/dev/null) || continue
				;;
			ec2)
				data="$mntdir/ec2/$version"
				metadata=meta-data.json
				value=$(yx -f "$data/$metadata" -- instance-id 2>/dev/null) || continue
				;;
		esac
		case "$value" in ''|null) continue;; esac
		loaded=1
		break
	done
	[ -n "$loaded" ] || return 1

	stagedir=$(mktemp -d "$TINY_CLOUD_RUN/$provider-volume.XXXXXX") || return 2
	case "$format" in
		native) set -- meta_data.json user_data network_data.json vendor_data.json vendor_data2.json;;
		ec2) set -- meta-data.json user-data;;
	esac
	for metadata; do
		[ -f "$data/$metadata" ] || continue
		cp "$data/$metadata" "$stagedir/$metadata" || return 2
	done
	printf '%s\n' "$format" > "$stagedir/format" || return 2
	metadata_volume_umount "$mntdir" || return 2
	mounted=
	mv "$stagedir" "$directory" || return 2
	stagedir=
)

# Explicit disk selections have their own snapshots, keyed by format and version.
configdrive_directory() {
	local provider="$1" format="$2" version="$3"
	if [ "${IMDS_SOURCE:-auto}" = configdrive ]; then
		case "$version" in ''|.*|*[!A-Za-z0-9_.-]*) return 1;; esac
		printf '%s\n' "$TINY_CLOUD_RUN/$provider-configdrive-$format-$version"
	else
		printf '%s\n' "$TINY_CLOUD_RUN/$provider-configdrive"
	fi
}

configdrive_select_source() (
	local provider="$1" format="$2" version="$3"
	local source rv attempts=0 tmpfile directory
	local marker="$TINY_CLOUD_RUN/$provider-source"

	case "$format" in auto|native|ec2) ;; *) return 1;; esac
	case "${IMDS_SOURCE:-auto}" in
		auto|configdrive) ;;
		http) printf '%s\n' http; return 0;;
		*) echo "imds: invalid metadata source '$IMDS_SOURCE'" >&2; return 1;;
	esac
	directory=$(configdrive_directory "$provider" "$format" "$version") || return

	exec 8>"$TINY_CLOUD_RUN/$provider-source.lock" || return
	while ! flock -n 8; do
		attempts=$((attempts + 1))
		if [ "$attempts" -ge 300 ]; then
			log err -s "Timed out waiting for ConfigDrive source selection"
			return 1
		fi
		sleep 0.1
	done
	if [ "${IMDS_SOURCE:-auto}" = configdrive ]; then
		if [ ! -f "$directory/format" ]; then
			configdrive_load "$provider" "$format" "$version" "$directory" || return
		fi
		printf '%s\n' configdrive
		return 0
	fi

	if [ -f "$marker" ]; then
		source=$(cat "$marker") || return
	else
		# A previous interrupted selection may have copied the drive already.
		if [ -f "$directory/format" ]; then
			source=configdrive
		elif configdrive_load "$provider" "$format" "$version" "$directory"; then
			source=configdrive
		else
			rv=$?
			[ "$rv" -eq 1 ] || return "$rv"
			source=http
		fi
		tmpfile=$(mktemp "$marker.XXXXXX") || return
		printf '%s\n' "$source" > "$tmpfile" && mv "$tmpfile" "$marker" || {
			rm -f "$tmpfile"
			return 1
		}
	fi
	case "$source" in
		http|configdrive) printf '%s\n' "$source";;
		*) log err -s "Invalid saved ConfigDrive source"; return 1;;
	esac
)

configdrive_document() {
	local directory="$1" path="$2" document file selector value

	case "$path" in
		''|/*) echo "imds: invalid document path '$path'" >&2; return 1;;
	esac
	case "/$path/" in
		*/../*|*/./*)
			echo "imds: invalid document path '$path'" >&2; return 1;;
	esac
	document="${path%%/*}"
	case "$document" in
		*[!A-Za-z0-9_.-]*)
			echo "imds: invalid document name '$document'" >&2; return 1;;
	esac
	file="$directory/$document"
	[ -f "$file" ] || return 1
	if [ "$path" = "$document" ]; then
		cat "$file"
		return
	fi

	selector="${path#*/}"
	set --
	while [ -n "$selector" ]; do
		set -- "$@" "${selector%%/*}"
		case "$selector" in
			*/*) selector="${selector#*/}";;
			*) break;;
		esac
	done
	value=$(yx -f "$file" -- "$@") || return
	case "$value" in
		''|null) echo "imds: empty metadata value '$path'" >&2; return 1;;
	esac
	printf '%s\n' "$value"
}

# Native keys are document members; EC2 members contain an openssh-key field.
configdrive_ssh_keys() {
	local directory="$1" path="$2" format="$3" keys key value result=

	keys=$(configdrive_document "$directory" "$path") || return
	while IFS= read -r key; do
		[ "$format" != ec2 ] || key="$key/openssh-key"
		value=$(configdrive_document "$directory" "$path/$key") || return
		result="${result}${value}
"
	done <<-EOT
		$keys
	EOT
	printf '%s' "$result" | sort -u
}
