#!/bin/sh

PROG=mkmodpack
VERSION=0.1
CATCHED=

Exit()
{
    local rc=$?
    [ -z "$1" ] || rc="$1"
    CATCHED=1
    exit $rc
}

Warning()
{
    echo "$PROG: warning: $*" >&2
}

Fatal()
{
    echo "$PROG: $*" >&2
    Exit 1
}

KERNEL=
OUTPUT=
WORKDIR=
TEMPLATE=
pattern=
uname_r="$(uname -r)"

exit_handler()
{
    local rc=$?
    trap - EXIT
    [ -n "$CATCHED" -o $rc -eq 0 ] ||
    echo "$PROG: unhandled error, exiting..."
    [ -z "$WORKDIR" ] || rm -rf "$WORKDIR"
    [ -z "$TEMPLATE" ] || rm -f "$TEMPLATE"
    exit $rc
}

signal_handler()
{
    echo 'Interrupted!' >&2
    Exit 1
}

trap exit_handler EXIT
trap signal_handler SIGHUP SIGPIPE SIGINT SIGTERM SIGQUIT

FIRMWARE_DIRS="/lib/firmware /usr/lib/hotplug/firmware"

Usage()
{
	cat >&2 <<EOF
mkmodpack - creates an cpio archive, containing subset of kernel modules.

mkmodpack is free software, covered by the GNU General Public License.
mkmodpack comes with ABSOLUTELY NO WARRANTY, see license for details.

Usage: $PROG [options]

Valid options are:
--version                       print version number and exit.
-k, --kernel			assume given kernel version, instead of uname -r.
-o, --output			output filename, stdout if omitted.
-p, --pattern			place into archive only modules which match one of pattern from given file.
-h, --help                      show this text.

Example: $PROG -o modpack -k $uname_r

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

#---------------------------------------------------------------
ListModules()
{
    if [ -s "$pattern" ]; then
	find /lib/modules/$KERNEL -type f 2>/dev/null |grep -wf "$pattern"
    else
	find /lib/modules/$KERNEL -type f 2>/dev/null
    fi
}

ListModuleFiles()
{
    modprobe --set-version="$KERNEL" --show-depends "$@" 2>/dev/null
}

AddUniqueModule()
{
    local path="$1" modules=
    modules="$MODULES
$path"
    modules="$(echo "$modules" |sort)"
    if [ -z "$(echo "$modules" |uniq -d)" ]; then
	echo "$modules"
    fi
}

AddModuleFile()
{
    local path="$1" name="$1" modules=
    name="${name##*/}"
    name="${name%.gz}"
    name="${name%.xz}"
    name="${name%.ko}"

    modules=$(AddUniqueModule "$path")
    if [ -n "$modules" ]; then
	MODULES="$modules"
	AddModuleFirmware "$name" "$path"
    fi
}

AddModuleFirmware()
{
    local mod_name="$1" && shift
    local mod_path="$1" && shift
    local fw_list fw_name fw_dir fw_file fw_privdir

    fw_list="$(modinfo -F firmware "$mod_path")" || return
    [ -n "$fw_list" ] || return
    for fw_name in $fw_list; do
	fw_file=
	for fw_dir in $FIRMWARE_DIRS; do
	    if [ -r "$fw_dir/$fw_name" ]; then
		fw_file="$fw_dir/$fw_name"
		break
	    fi
	done
	[ -n "$fw_file" ] || {
	    Warning "Firmware file \"$fw_name\" for module \"$mod_name\" not found"
	    continue
	}
	[ "$fw_name" = "${fw_name##*/}" ] || {
	    [ -n "$fw_privdir" ] || {
		fw_privdir="${fw_name%/*}"
		FILES="$FILES
dir	/lib/firmware/$fw_privdir	0755	0 0"
	    }
	}
	FILES="$FILES
file	/lib/firmware/$fw_name	$fw_file	0644	0 0"
    done
}

AddModule()
{
    local path="$1" name="$1" m list
    name="${name##*/}"
    name="${name%.gz}"
    name="${name%.xz}"
    name="${name%.ko}"
    
    list=`ListModuleFiles "$name"`
    for m in $list; do
	[ -z "${m##/lib/modules/*}" ] || continue
	AddModuleFile "$m"
    done
}

TEMP=`getopt -n "$0" -o k:,o:,p:,h -l kernel:,output:,pattern:,help,version -- "$@"` || exit 1
eval set -- "$TEMP"

while :; do
    case "$1" in
	-k|--kernel)
	shift
	KERNEL="$1"
	;;
	-o|--output)
	shift
	OUTPUT="$1"
	;;
	-p|--pattern*)
	shift
	pattern="$1"
	;;
	-h|--help)
	Usage 0
	;;
	--version)
	echo "$PROG: version $VERSION"
	exit 0
	;;
	--) shift; break
	;;
	*) Fatal "Unrecognized option: $1"
	;;
    esac
    shift
done

[ -n "$KERNEL" ] || KERNEL="$uname_r"
[ -d "/lib/modules/$KERNEL" ] || Fatal "Directory /lib/modules/$KERNEL does not exists."

# modprobe from kmod-9+ *requires* modules.dep.bin (ALT #27640)
if [ ! -f /lib/modules/$KERNEL/modules.dep.bin ]; then
	depmod -a -F "/boot/System.map-$KERNEL" "$KERNEL" ||
	Fatal "Failed to create modules dependencies (full)."
fi

#---------------------------------------------------------------
FIRMWARE_DIRS="$FIRMWARE_DIRS /lib/firmware/$KERNEL"
MODULES=
FILES=

for m in $(ListModules); do
    AddModule "$m"
done

WORKDIR=`mktemp -td mkmodpack.XXXXXXXXXX` || Fatal "Failed to create working directory."
TEMPLATE=`mktemp -t mkmodpack.XXXXXXXXXX` || Fatal "Failed to create temporary file."

echo "$MODULES" |cpio -pmd "$WORKDIR" 2>/dev/null || Fatal "Failed to copy modules to working directory."
depmod -a -F "/boot/System.map-$KERNEL" -b "$WORKDIR" "$KERNEL" || Fatal "Failed to create modules dependencies."

(
cd "$WORKDIR"
find "lib/modules/$KERNEL" -type d |sed -e 's,^.\+$,dir\t/&\t0755\t0 0,g'
find "lib/modules/$KERNEL" -type f |sed -e "s,^.\+$,file\t/&\t$WORKDIR/&\t0644\t0 0,g"
echo "$FILES"
) > "$TEMPLATE"

if [ -n "$OUTPUT" ]; then
    gencpio "$TEMPLATE" |gzip -c > "$OUTPUT"
else
    gencpio "$TEMPLATE" |gzip -c
fi

Exit 0
