#!/bin/sh
#
# Find missing firmware files requested by the kernel, and install the
# binary packages with the files.  Fetch them the from the non-free section
# if that is needed.
#
# This script was copied from auto-addfirmware from the debian-edu-config
# package and extended here.

PATH=/sbin:/usr/sbin:$PATH
export PATH

dist=$(lsb_release -cs)
if [ "n/a" = "$dist" ] ; then
    dist=testing
fi
arch=$(dpkg --print-architecture)
mirror=http://deb.debian.org/debian
aptsourcelist=/etc/apt/sources.list.d/isenkram-autoinstall-firmware-nf.list
aptsourcelist2=/etc/apt/sources.list.d/isenkram-autoinstall-firmware.list

if [ "-l" = "$1" ] ; then
    listonly=true
else
    listonly=false
fi

loginfo() {
    echo "info: $@" 1>&2
}

add_nonfree_firmware() {
    if $listonly ; then
	return
    fi
    cat <<EOF > $aptsourcelist
deb $mirror $dist non-free-firmware
deb-src $mirror $dist non-free-firmware
EOF
    chmod a+r $aptsourcelist
    echo "apt updated" > $nonfreeflag
}
add_contrib_nonfree() {
    if $listonly ; then
	return
    fi
    cat <<EOF > $aptsourcelist2
deb $mirror $dist contrib non-free
deb-src $mirror $dist contrib non-free
EOF
    chmod a+r $aptsourcelist2
    echo "apt updated" > $nonfreeflag
}

appstreamlookup() {
    fwfile="$1"
    LC_ALL=C appstreamcli what-provides firmware:runtime "$fwfile" | \
	awk '/Package:/ { print $2}'
}

# Find firmware files requested by loaded kernel drivers.
for fwfile in $(for module in $(awk '{print $1}' /proc/modules) ; do modinfo $module 2>/dev/null |awk '/^firmware:/ {print $2}'; done | LC_ALL=C sort -u); do
    if [ ! -e "/lib/firmware/$fwfile" ] ; then
       fwfiles="${fwfiles:+$fwfiles }$fwfile"
    fi
done

# Also look in dmesg for requested firmware for modules that refuse to
# load without their firmware.  See also bug #725714.
if dmesg >/dev/null 2>&1 ; then
    for fwfile in $(dmesg | sed -rn 's/^(\[[^]]*\] )?([^ ]+) [^ ]+: firmware: failed to load ([^ ]+) .*/\3/p'); do
	loginfo "looking for firmware file $fwfile requested by kernel"
	if [ ! -e "/lib/firmware/$fwfile" ] ; then
            fwfiles="${fwfiles:+$fwfiles }$fwfile"
	fi
    done
else
    loginfo "unable to check dmesg, list might be incomplete"
fi

if [ -z "$fwfiles" ] ; then
    loginfo "did not find any firmware files requested by loaded kernel modules.  exiting"
    exit 1
fi
loginfo "some kernel driver requested extra firmware files:" $fwfiles

tmpdir=$(mktemp -d)
nonfreeflag=nonfreeflag
cd $tmpdir || exit 1

cleanup() {
    cd /
    rm -rf $tmpdir
}

# Prefer appstream and use the local copy of firmware->package
# mappings if nothing is found using appstream.

find_local_data() {
    if [ -z "$datadist" ] ; then
	datadist="$dist"
    fi
    datafiles=""
    for a in $arch all; do
        for f in Fw-Contents-$a-$datadist-* \
                 /usr/share/isenkram/Fw-Contents-$a-$datadist-* ; do
            if [ -s "$f" ] ; then
                datafiles="$datafiles $f"
            fi
        done
    done
}

find_local_data
if [ -z "$datafiles" ] ; then
    loginfo "unable to find any local firmware info for dist $dist, using info for sid"
    datadist=sid
    find_local_data
fi

loginfo "locating packages with the requested firmware files"
binpkginfos=""
binpkgs=""
for fwfile in $fwfiles ; do
    # First check appstream, next look in our local files if they exist
    binpkginfo=$(appstreamlookup $fwfile)
    if [ -z "$binpkginfo" ] && [ "$datafiles" ]; then
	fwfilere=$(echo $fwfile | sed -e 's%/%\\/%g' -e 's/\./\\./g')
	binpkginfo="$(awk "/^lib\/firmware\/$fwfilere/ {print \$2}" $datafiles)"
    fi
    if [ -z "$binpkginfo" ] ; then
	# Special case for b43 where the firmware is
	# undistributable by Debian.
	case "$fwfile" in
	    b43/*)
		add_contrib_nonfree
		binpkgs="${binpkgs:+$binpkgs }firmware-b43-installer"
		;;
	esac
    else
	binpkginfos="$binpkginfos $binpkginfo"
    fi
done

# Many different files can be required for a single hardware device,
# plus several devices might require files from the same package, so
# optimize!
binpkginfos=$(echo "$binpkginfos" | tr ' ' '\n' | LC_ALL=C sort -u | xargs)

loginfo "determining whether enabling other components is required"
binpkgs="${binpkgs:+$binpkgs }$(
for binpkginfo in $binpkginfos ; do
    echo $binpkginfo |  while IFS=/ read section srcpkg binpkg ; do
        echo $binpkg
# Enable the non-free-firmware or non-free section if it is needed
	if ! LC_ALL=C apt-get install -s $binpkg >/dev/null 2>&1; then
	    if [ non-free-firmware = "$section" ] ; then
	        add_nonfree_firmware
            elif [ non-free = "$section" ] \
                 || [ contrib = "$section" ] ; then
	        add_contrib_nonfree
	    fi
	fi
    done
done)"

if [ -e $nonfreeflag ] && ! $listonly ; then
    # Fetch updated package lists
    loginfo "Updating APT sources after adding non-free APT source"
    apt-get -qq update
fi

if [ "$binpkgs" ] ; then
    if $listonly ; then
	echo $binpkgs
    else
        # Install firmware packages
	loginfo "trying to install $binpkgs"
	apt-get -qq install -y $binpkgs
    fi
else
    loginfo "No new firmware package with requested firmware detected."
fi
cleanup
