#!/bin/bash
#
# weak-modules - determine which modules are kABI compatible with installed
# kernels and set up the symlinks in /lib/*/weak-updates.
#
unset LANG LC_ALL LC_COLLATE
tmpdir=$(mktemp -td ${0##*/}.XXXXXX)
trap "rm -rf $tmpdir" EXIT
unset ${!changed_modules_*} ${!changed_initrd_*}
#!/bin/sh
# rpmsort: The sort in coreutils can't sort the RPM list how we want it so we
# instead transform the list into a form it will sort correctly, then sort.
rpmsort() {
rpmlist=($(cat))
if [ "$1" == "-r" ]; then
reverse=1
else
reverse=0
fi
if [ ${#rpmlist[@]} = 1 ]; then
# By definition already sorted
return
fi
# Hash every value in the rpmlist
for ((i = 0; i < ${#rpmlist[@]}; i++)); do
rpmhash[i]="$(echo ${rpmlist[i]} | awk '{decode="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."; split($1,tmp,"");for (i=1;i<=length($1);i++){ printf "%02x",index(decode,tmp[i]) } }')"
done
for ((i = 0; i < ${#rpmlist[@]}; i++)); do
for ((j = 0; j < ${#rpmlist[@]}-1; j++)); do
rpm1=${rpmlist[j]} rpm2=${rpmlist[j+1]}
hex1=${rpmhash[j]} hex2=${rpmhash[j+1]}
if [ ${#rpm1} -lt ${#rpm2} ]; then
len=$((${#rpm1}*2));
else
len=$((${#rpm2}*2));
fi
if [ "$(printf "%s\n" ${hex1:0:$len} ${hex2:0:$len} \
| sort | head -n 1)" = "$hex2" ]; then
rpmlist[j]=$rpm2; rpmlist[j+1]=$rpm1
rpmhash[j]=$hex2; rpmhash[j+1]=$hex1
fi
done
done
if [ $reverse = 0 ]; then
for ((n = 0; n < ${#rpmlist[@]} ; n++)); do
echo "${rpmlist[$n]}"
done
else
for ((n = ${#rpmlist[@]}-1; n >= 0; n--)); do
echo "${rpmlist[$n]}"
done
fi
}
# read_modules_list:
# Read in a list of modules from standard input. Convert the filenames into
# absolute paths and compute the kernel release for each module (either using
# the modinfo section or through the absolute path.
read_modules_list() {
local IFS=$'\n'
modules=($(cat))
for ((n = 0; n < ${#modules[@]}; n++)); do
if [ ${modules[n]:0:1} != '/' ]; then
modules[n]="$PWD/${modules[n]}"
fi
if [ -f "${modules[n]}" ]; then
module_krels[n]=$(krel_of_module ${modules[n]})
else
# Try to extract the kernel release from the path
set -- "${modules[n]#/lib/modules/}"
module_krels[n]=${1%%/*}
fi
done
}
# krel_of_module:
# Compute the kernel release of a module.
krel_of_module() {
declare module=$1
/sbin/modinfo -F vermagic "$module" | awk '{print $1}'
}
# module_is_compatible:
# Determine if a module is compatible with a particular kernel release. Also
# include any symbol deps that might be introduced by other external KMPs.
module_is_compatible() {
declare module=$1 krel=$2 module_krel=$(krel_of_module "$module")
if [ ! -e "$tmpdir/all-symvers-$krel-$module_krel" ]; then
# Symbols exported by the "new" kernel
if [ ! -e $tmpdir/symvers-$krel ]; then
if [ -e /boot/symvers-$krel.gz ]; then
zcat /boot/symvers-$krel.gz \
| sed -r -ne 's:^(0x[0-9a-f]+\t[0-9a-zA-Z_]+)\t.*:\1:p'
fi > $tmpdir/symvers-$krel
fi
# Symbols that other add-on modules of the "old" kernel export
# (and that this module may require)
if [ ! -e "$tmpdir/extra-symvers-$module_krel" ]; then
if [ -e /lib/modules/$module_krel/extra ]; then
find /lib/modules/$module_krel/extra -name '*.ko' \
| xargs nm \
| sed -nre 's:^([0-9a-f]+) A __crc_(.*):0x\1 \2:p'
fi > $tmpdir/extra-symvers-$module_krel
fi
sort -u $tmpdir/symvers-$krel $tmpdir/extra-symvers-$module_krel \
> "$tmpdir/all-symvers-$krel-$module_krel"
fi
# If the module does not have modversions enabled, $tmpdir/modvers
# will be empty.
/sbin/modprobe --dump-modversions "$module" \
| sed -r -e 's:^(0x[0-9a-f]+\t.*):\1:' \
| sort -u \
> $tmpdir/modvers
# Only include lines of the second file in the output that don't
# match lines in the first file. (The default separator is
# <space>, so we are matching the whole line.)
join -j 1 -v 2 $tmpdir/all-symvers-$krel-$module_krel \
$tmpdir/modvers > $tmpdir/join
if [ ! -s $tmpdir/modvers ]; then
echo "Warning: Module ${module##*/} from kernel $module_krel has no" \
"modversions, so it cannot be reused for kernel $krel" >&2
elif [ -s $tmpdir/join ]; then
[ -n "$verbose" ] &&
echo "Module ${module##*/} from kernel $module_krel is not compatible" \ "with kernel $krel in symbols:" $(sed -e 's:.* ::' $tmpdir/join)
else
[ -n "$verbose" ] &&
echo "Module ${module##*/} from kernel $module_krel is compatible" \
"with kernel $krel"
return 0
fi
return 1
}
# doit:
# A wrapper used whenever we're going to perform a real operation.
doit() {
[ -n "$verbose" ] && echo "$@"
[ -n "$dry_run" ] || "$@"
}
usage() {
echo "Usage: ${0##*/} [options] {--add-modules|--remove-modules}"
echo "${0##*/} [options] {--add-kernel|--remove-kernel} {kernel-release}"
cat <<'EOF'
--add-modules
Add a list of modules read from standard input. Create
symlinks in compatible kernel's weak-updates/ directory.
The list of modules is read from standard input.
--remove-modules
Remove compatibility symlinks from weak-updates/ directories
for a list of modules. The list of modules is read from
standard input.
--add-kernel
Add compatibility symlinks for all compatible modules to the
specified or running kernel.
--remove-kernel
Remove all compatibility symlinks for the specified or current
kernel.
--verbose
Print the commands executed.
-dry-run
Do not create/remove any files.
EOF
exit $1
}
# module_has_changed:
# Mark if an actual change occured that we need to deal with later by calling
# depmod or mkinitrd against the affected kernel.
module_has_changed() {
declare module=$1 krel=$2
module=${module%.ko}
module=${module##*/}
eval "changed_modules_${krel//[^a-zA-Z0-9]/_}=$krel"
eval "changed_initrd_${krel//[^a-zA-Z0-9]/_}=$krel"
}
# add_modules:
# Read in a list of modules from stdinput and process them for compatibility
# with installed kernels under /lib/modules.
add_modules() {
read_modules_list || exit 1
if [ ${#modules[@]} -gt 0 ]; then
for krel in $(ls /lib/modules/); do
[ -e "/boot/symvers-$krel.gz" ] || continue
for ((n = 0; n < ${#modules[@]}; n++)); do
module="${modules[n]}"
module_krel="${module_krels[n]}"
case "$module" in
/lib/modules/$krel/*)
continue ;;
esac
subpath="${module#/lib/modules/$module_krel/extra}"
weak_module="/lib/modules/$krel/weak-updates/${subpath#/}"
if [ -r "$weak_module" ]; then
weak_krel=$(krel_of_module "$weak_module")
if [ "$weak_krel" != "$module_krel" ] &&
[ "$(printf "%s\n" "$weak_krel" "$module_krel" \
| rpmsort | head -n 1)" = \
"$module_krel" ]; then
# Keep modules from more recent kernels.
[ -n "$verbose" ] && echo \
"Keeping module ${module##*/} from kernel $weak_krel for kernel $krel"
continue
fi
fi
if module_is_compatible $module $krel; then
doit mkdir -p $(dirname $weak_module)
doit ln -sf $module $weak_module
module_has_changed $module $krel
fi
done
done
fi
}
# remove_modules:
# Read in a list of modules from stdinput and process them for removal.
remove_modules() {
read_modules_list || exit 1
if [ ${#modules[@]} -gt 0 ]; then
krels=($(ls /lib/modules/ | rpmsort -r))
for krel in "${krels[@]}"; do
[ -e "/boot/symvers-$krel.gz" ] || continue
for ((n = 0; n < ${#modules[@]}; n++)); do
module="${modules[n]}"
module_krel="${module_krels[n]}"
subpath="${module#/lib/modules/$module_krel/extra}"
weak_module="/lib/modules/$krel/weak-updates/${subpath#/}"
if [ "$module" == "`readlink $weak_module`" ]; then
[ -n "$verbose" ] && echo \
"Removing compatible module ${module##*/} from kernel $krel"
doit rm -f "$weak_module"
for krel2 in "${krels[@]}"; do
[ -e "/boot/symvers-$krel2.gz" ] || continue
module="/lib/modules/$krel2/extra/$subpath"
[ -e "$module" ] || continue
if module_is_compatible "$module" "$krel2"; then
[ -n "$verbose" ] && echo \
"Adding compatible module ${module##*/} from kernel $krel2 instead"
doit ln -s "$module" "$weak_module"
break
fi
done
doit rmdir --parents --ignore-fail-on-non-empty \
"$(dirname "$weak_module")"
module_has_changed $module $krel
fi
done
done
fi
}
add_kernel() {
add_krel=${1:-$(uname -r)}
if [ ! -e "/boot/symvers-$add_krel.gz" ]; then
echo "Symvers dump file /boot/symvers-$add_krel.gz" \
"not found" >&2
exit 1
fi
for krel in $(ls /lib/modules/ | rpmsort -r); do
[ "$add_krel" = "$krel" ] && continue
[ -d /lib/modules/$krel/extra ] || continue
for module in $(find /lib/modules/$krel/extra -name '*.ko'); do
subpath="${module#/lib/modules/$krel/extra}"
weak_module="/lib/modules/$add_krel/weak-updates/${subpath#/}"
[ -e "$weak_module" ] && continue
if module_is_compatible $module $add_krel; then
doit mkdir -p $(dirname $weak_module)
doit ln -sf $module $weak_module
fi
done
done
}
remove_kernel() {
remove_krel=${1:-$(uname -r)}
weak_modules="/lib/modules/$remove_krel/weak-updates"
doit rm -rf "$weak_modules"
}
################################################################################
################################## MAIN GUTS ###################################
################################################################################
options=`getopt -o h --long help,add-modules,remove-modules \
--long add-kernel,remove-kernel,dry-run,verbose -- "$@"`
[ $? -eq 0 ] || usage 1
eval set -- "$options"
while :; do
case "$1" in
--add-modules)
do_add_modules=1
;;
--remove-modules)
do_remove_modules=1
;;
--add-kernel)
do_add_kernel=1
;;
--remove-kernel)
do_remove_kernel=1
;;
--dry-run)
dry_run=1
;;
--verbose)
verbose=1
;;
-h|--help)
usage 0
;;
--)
shift
break
;;
esac
shift
done
if [ -n "$do_add_modules" ]; then
add_modules
elif [ -n "$do_remove_modules" ]; then
remove_modules
elif [ -n "$do_add_kernel" ]; then
kernel=${1:-$(uname -r)}
add_kernel $kernel
elif [ -n "$do_remove_kernel" ]; then
kernel=${1:-$(uname -r)}
remove_kernel $kernel
else
usage 1
fi
################################################################################
###################### CLEANUP POST ADD/REMOVE MODULE/KERNEL ###################
################################################################################
# run depmod and mkinitrd as needed
for krel in ${!changed_modules_*}; do
krel=${!krel}
doit /sbin/depmod -ae -F /boot/System.map-$krel $krel
done
for krel in ${!changed_initrd_*}; do
krel=${!krel}
doit /sbin/mkinitrd --allow-missing -f /boot/initrd-$krel.img $krel
done