#!/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 "$@"