#!/bin/sh -e
#
# Copyright (C) 2000-2004, 2009  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2003  Peter Novodvorsky <nidd@altlinux.com>
# Copyright (C) 2004-2006  Sergey Vlasov <vsu@altlinux.org>
#
# Linux kernel headers version selection utility.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

PROG=adjust_kernel_headers
VERSION='1.2.4'

fatal()
{
	echo "$PROG: $*" >&2
	exit 1
}

list_kernels()
{
	local list1="$(ls -d /usr/include/linux-*/include/linux/version.h 2>/dev/null |
		while read f; do
			find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null
		done |
		sed 's,^/usr/include/linux-\([^/]\+\)/include/linux/version\.h$,\1,g')"
	local list2="$(ls -d /usr/lib/kernel/2.*/include/linux/version.h 2>/dev/null |
		while read f; do
			find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null
		done |
		sed 's,^/usr/lib/kernel/\([^/]\+\)/include/linux/version\.h$,\1,g')"
	local list3=""
	local list4=""
	if [ -f /usr/lib/kernel/include/asm/unistd.h ]; then
		list3="$(ls -d /var/lib/kernel/2.*/version.h 2>/dev/null |
			while read f; do
				find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null
			done |
			sed 's,^/var/lib/kernel/\([^/]\+\)/version\.h$,\1,g')"
		list4="$(ls -d /usr/lib/kernel/2.*/version.h 2>/dev/null |
			while read f; do
				find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null
			done |
			sed 's,^/usr/lib/kernel/\([^/]\+\)/version\.h$,\1,g')"
	fi
	(echo "$list1"; echo "$list2"; echo "$list3"; echo "$list4") |
		sort -u |
		awk '
			BEGIN { seen_default = 0 }
			/^$/ { next }
			/^default$/ { seen_default = 1; next }
			{ print }
			END { if (seen_default) print "default" }
		'
}

usage()
{
	[ "$1" = 0 ] || exec >&2
	cat <<EOF
$PROG - select version of kernel headers.

Usage: $PROG [OPTION | KERNEL-HEADERS-VERSION]

Valid options are:
  --auto            switch to automatic mode: use headers for the running
                      kernel if available, or default headers otherwise;
  --first           use first kernel version in the alphabetic order;
  --list            show available versions of kernel headers;
  --help            display this help and exit;
  --version         output version information and exit.

Available versions: auto $(list_kernels |tr -s '[:space:]' ' ')
    
Report bugs to http://bugs.altlinux.ru/

EOF
	[ -n "$1" ] && exit "$1" || exit
}

gen_kernel_hdrs()
{
	local destdir=/var/run/kernel
	cd "$destdir" && [ -w . ]

	local dir=$1
	shift

	local n
	for n in autoconf modversions version; do
		local inc_line="#include <$dir/$n.h>"
		fgrep -qs "$inc_line" "$n.h" ||
		cat >"$n.h" << EOF
/*
 * Autogenerated by $PROG at $(LANG=C date '+%a %b %d %Y %T')
 */

#ifndef	__boot_kernel_${n}__
#define	__boot_kernel_${n}__

$inc_line

#endif	/* __boot_kernel_${n}__ */
EOF
	done

	H2PH=/usr/bin/h2ph
	if [ -x "$H2PH" ]; then
		"$H2PH" -l -Q -d "$destdir" {autoconf,modversions,version}.h ||:
	fi
}

KERNEL_INCLUDE=/etc/sysconfig/kernel/include

kernel_headers_configured()
{
	[ -f "$KERNEL_INCLUDE/asm/unistd.h" ] || return 1
	[ -f "$KERNEL_INCLUDE/linux/version.h" ] || return 1
	[ -n "$(find "$KERNEL_INCLUDE/linux/version.h" -maxdepth 1 -type f -size +90c 2>/dev/null)" ] || return 1
	return 0
}

try_adjust_new()
{
	local dir="$1"
	shift

	local f="$dir/include/linux/version.h"
	[ -f "$f" ] &&
		[ -n "$(find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null)" ] ||
		return 0

	ln -snf "$dir/include" "$KERNEL_INCLUDE"
	exit
}

try_adjust_old()
{
	local dir="$1"
	shift

	local f="$dir/version.h"
	[ -f "$f" ] &&
		[ -n "$(find "$f" -maxdepth 1 -type f -size +90c 2>/dev/null)" ] &&
		[ -f /usr/lib/kernel/include/asm/unistd.h ] ||
		return 0

	ln -snf /usr/lib/kernel/include "$KERNEL_INCLUDE"
	gen_kernel_hdrs "$dir"
	exit
}

try_adjust_all()
{
	try_adjust_new "/usr/include/linux-$1"
	try_adjust_new "/usr/lib/kernel/$1"
	try_adjust_old "/var/lib/kernel/$1"
	try_adjust_old "/usr/lib/kernel/$1"
}

# At most one argument, please.
[ "$#" -le 1 ] || usage

case "$1" in
	help|--help|usage|--usage)
		usage 0
		;;
	--version)
		echo "$PROG: version $VERSION"
		exit 0
		;;
	list|--list)
		list_kernels
		exit 0
		;;
	first|--first)
		first="$(list_kernels |head -1)"
		[ -n "$first" ] ||
			fatal "$1: unable to locate \"first\" kernel headers"
		touch /etc/sysconfig/kernel/include_manual_mode
		try_adjust_all "$first"
		fatal "$1: first kernel headers are not installed"
		;;
	'')
		if [ -f /etc/sysconfig/kernel/include_manual_mode ] && kernel_headers_configured; then
			exit 0
		fi
		;;
	auto|--auto)
		;;
	*)
		[ -n "${1##*/*}" ] &&
			[ "$1" = default -o "$1" = libc-headers -o -z "${1##[2-9].[0-9]*}" ] ||
			fatal "$1: invalid kernel version"
		touch /etc/sysconfig/kernel/include_manual_mode
		try_adjust_all "$1"
		fatal "$1: kernel headers for this version are not installed"
		;;
esac

rm -f /etc/sysconfig/kernel/include_manual_mode

FULL_KERNEL_VERSION="$(uname -r)"
KERNEL_VERSION="$(echo "$FULL_KERNEL_VERSION" |sed 's/-alt[[:digit:]]\+\(\.M[[:digit:]]\+\)\?\(\.[[:digit:]]\+\)\?//')"

try_adjust_new "/usr/include/linux-$KERNEL_VERSION"
try_adjust_new "/usr/lib/kernel/$KERNEL_VERSION"
try_adjust_old "/var/lib/kernel/$FULL_KERNEL_VERSION"
try_adjust_old "/usr/lib/kernel/$FULL_KERNEL_VERSION"
try_adjust_new /usr/include/linux-libc-headers
try_adjust_new /usr/include/linux-default
exit 1
