#!/bin/sh

set -eu

: "${ROOT:=}"
: "${OS_RELEASE_PRIMARY:=@@OS_RELEASE_PRIMARY@@}"
: "${OS_RELEASE_FALLBACK:=@@OS_RELEASE_FALLBACK@@}"
: "${PROC_OSRELEASE:=@@PROC_OSRELEASE@@}"

progname=${0##*/}

error() {
	printf '%s\n' "$progname: $*" >&2
	exit 1
}

usage() {
	printf '%s\n' "usage: $progname [-kru] [-j jail]" >&2
	exit 1
}

root_path() {
	case $1 in
	/*)
		if [ -n "$ROOT" ]; then
			printf '%s%s\n' "$ROOT" "$1"
		else
			printf '%s\n' "$1"
		fi
		;;
	*)
		error "internal error: expected absolute path, got '$1'"
		;;
	esac
}

path_basename() {
	path=$1
	while [ "$path" != "/" ] && [ "${path%/}" != "$path" ]; do
		path=${path%/}
	done
	printf '%s\n' "${path##*/}"
}

extract_release_from_name() {
	case $1 in
	vmlinuz-*)
		printf '%s\n' "${1#vmlinuz-}"
		return 0
		;;
	bzImage-*)
		printf '%s\n' "${1#bzImage-}"
		return 0
		;;
	kernel-*)
		printf '%s\n' "${1#kernel-}"
		return 0
		;;
	esac
	return 1
}

append_unique_candidate() {
	candidate=$1
	[ -n "$candidate" ] || return 0

	if [ -z "${kernel_candidates:-}" ]; then
		kernel_candidates=$candidate
		return 0
	fi

	if printf '%s\n' "$kernel_candidates" | grep -Fx "$candidate" >/dev/null 2>&1; then
		return 0
	fi

	kernel_candidates=$kernel_candidates'
'$candidate
}

extract_release_from_link() {
	link_path=$1

	[ -L "$link_path" ] || return 1
	target=$(readlink "$link_path" 2>/dev/null) || return 1
	extract_release_from_name "$(path_basename "$target")"
}

collect_boot_kernel_candidates() {
	for absolute in /vmlinuz /boot/vmlinuz; do
		link_path=$(root_path "$absolute")
		if release=$(extract_release_from_link "$link_path"); then
			printf '%s\n' "$release"
			return 0
		fi
	done

	for absolute in /vmlinuz-* /boot/vmlinuz-* /boot/bzImage-* /boot/kernel-*; do
		pattern=$(root_path "$absolute")
		for entry in $pattern; do
			[ -e "$entry" ] || continue
			release=$(extract_release_from_name "$(path_basename "$entry")") || continue
			append_unique_candidate "$release"
		done
	done

	return 1
}

collect_module_tree_candidates() {
	for absolute in /lib/modules /usr/lib/modules; do
		modules_root=$(root_path "$absolute")
		[ -d "$modules_root" ] || continue

		for entry in "$modules_root"/*; do
			[ -d "$entry" ] || continue
			if [ ! -d "$entry/kernel" ] && [ ! -f "$entry/modules.dep" ]; then
				continue
			fi
			append_unique_candidate "$(path_basename "$entry")"
		done
	done
}

format_candidate_list() {
	printf '%s\n' "$kernel_candidates" | awk '
		NF {
			if (count > 0)
				printf(", ");
			printf("%s", $0);
			count++;
		}
		END {
			if (count > 0)
				printf("\n");
		}
	'
}

installed_kernel_version() {
	kernel_candidates=

	if release=$(collect_boot_kernel_candidates); then
		printf '%s\n' "$release"
		return 0
	fi

	collect_module_tree_candidates
	count=$(printf '%s\n' "${kernel_candidates:-}" | awk 'NF { count++ } END { print count + 0 }')

	case $count in
	0)
		error "unable to determine installed kernel version under '${ROOT:-/}'"
		;;
	1)
		printf '%s\n' "$kernel_candidates"
		;;
	*)
		error "unable to determine installed kernel version under '${ROOT:-/}': multiple kernel releases found ($(format_candidate_list))"
		;;
	esac
}

running_kernel_version() {
	if [ -r "$PROC_OSRELEASE" ]; then
		release=
		IFS= read -r release <"$PROC_OSRELEASE" || true
		[ -n "$release" ] || error "unable to determine running kernel version from $PROC_OSRELEASE"
		printf '%s\n' "$release"
		return 0
	fi

	if command -v uname >/dev/null 2>&1; then
		command uname -r
		return 0
	fi

	error "unable to determine running kernel version: $PROC_OSRELEASE is unavailable"
}

extract_os_release_field() {
	field=$1
	file=$2

	if output=$(awk -v want="$field" '
		function ltrim(s) {
			sub(/^[[:space:]]+/, "", s)
			return s
		}

		function rtrim(s) {
			sub(/[[:space:]]+$/, "", s)
			return s
		}

		function decode_quoted(s,    out, i, c, esc) {
			out = ""
			esc = 0
			for (i = 2; i <= length(s); i++) {
				c = substr(s, i, 1)
				if (esc) {
					out = out c
					esc = 0
					continue
				}
				if (c == "\\") {
					esc = 1
					continue
				}
				if (c == "\"") {
					if (rtrim(substr(s, i + 1)) != "")
						return "__TRAILING__"
					return out
				}
				out = out c
			}
			return "__UNTERMINATED__"
		}

		BEGIN {
			found = 0
			parse_error = 0
		}

		/^[[:space:]]*#/ || /^[[:space:]]*$/ {
			next
		}

		{
			line = $0
			sub(/^[[:space:]]+/, "", line)
			if (line !~ /^[A-Z0-9_]+=.*$/)
				next

			key = line
			sub(/=.*/, "", key)
			if (key != want)
				next

			value = line
			sub(/^[^=]*=/, "", value)
			value = ltrim(value)

			if (value ~ /^"/) {
				value = decode_quoted(value)
				if (value == "__TRAILING__" || value == "__UNTERMINATED__") {
					parse_error = 1
					exit
				}
				print value
				found = 1
				exit 0
			}

			if (value ~ /[[:space:]]/) {
				parse_error = 1
				exit
			}

			print rtrim(value)
			found = 1
			exit 0
		}

		END {
			if (parse_error)
				exit 3
			if (!found)
				exit 1
		}
	' "$file" 2>&1); then
		status=0
	else
		status=$?
	fi

	case $status in
	0)
		[ -n "$output" ] || return 1
		printf '%s\n' "$output"
		return 0
		;;
	1)
		return 1
		;;
	3)
		return 2
		;;
	*)
		return 3
		;;
	esac
}

userland_version() {
	primary=$(root_path "$OS_RELEASE_PRIMARY")
	fallback=$(root_path "$OS_RELEASE_FALLBACK")

	for file in "$primary" "$fallback"; do
		[ -r "$file" ] || continue

		for field in VERSION_ID BUILD_ID VERSION PRETTY_NAME; do
			if value=$(extract_os_release_field "$field" "$file"); then
				printf '%s\n' "$value"
				return 0
			else
				status=$?
				case $status in
				1)
					;;
				2)
					error "malformed os-release entry '$field' in $file"
					;;
				*)
					error "failed to parse $file"
					;;
				esac
			fi
		done

		error "unable to determine userland version from $file"
	done

	error "unable to locate os-release under '${ROOT:-/}'"
}

main() {
	opt_k=0
	opt_r=0
	opt_u=0
	opt_j=0

	OPTIND=1
	while getopts "kruj:" option; do
		case $option in
		k)
			opt_k=1
			;;
		r)
			opt_r=1
			;;
		u)
			opt_u=1
			;;
		j)
			opt_j=1
			;;
		*)
			usage
			;;
		esac
	done
	shift $((OPTIND - 1))

	[ $# -eq 0 ] || usage

	if [ $((opt_k + opt_r + opt_u + opt_j)) -eq 0 ]; then
		opt_u=1
	fi

	if [ "$opt_j" -ne 0 ]; then
		error "option -j is not supported on Linux"
	fi

	if [ "$opt_k" -ne 0 ]; then
		installed_kernel_version
	fi

	if [ "$opt_r" -ne 0 ]; then
		running_kernel_version
	fi

	if [ "$opt_u" -ne 0 ]; then
		userland_version
	fi
}

main "$@"
