Blame SOURCES/weak-modules

f1da6a
#!/bin/bash
f1da6a
#
f1da6a
# weak-modules - determine which modules are kABI compatible with installed
f1da6a
#                kernels and set up the symlinks in /lib/*/weak-updates.
f1da6a
#
f1da6a
# Changelog:
f1da6a
#
f1da6a
# 2010/01/10 - Further updates for dracut use on Fedora/RHEL (jcm).
f1da6a
# 2009/09/16 - Rebase and add a bunch of updates for dracut (jcm).
f1da6a
f1da6a
unset LANG LC_ALL LC_COLLATE
f1da6a
f1da6a
tmpdir=$(mktemp -td ${0##*/}.XXXXXX)
f1da6a
trap "rm -rf $tmpdir" EXIT
f1da6a
unset ${!changed_modules_*} ${!changed_initramfs_*}
f1da6a
f1da6a
initramfs_prefix="/boot" # can customize here
f1da6a
dracut="/sbin/dracut"
f1da6a
f1da6a
if [ ! -x "$dracut" ]
f1da6a
then
f1da6a
        echo "weak-modules: this tool requires a dracut-enabled kernel"
f1da6a
	exit 1
f1da6a
fi
f1da6a
f1da6a
# doit:
f1da6a
# A wrapper used whenever we're going to perform a real operation.
f1da6a
doit() {
f1da6a
    [ -n "$verbose" ] && echo "$@"
f1da6a
    [ -n "$dry_run" ] || "$@"
f1da6a
}
f1da6a
f1da6a
# rpmsort: The sort in coreutils can't sort the RPM list how we want it so we
f1da6a
# instead transform the list into a form it will sort correctly, then sort.
f1da6a
rpmsort() {
f1da6a
	local IFS=$' '
f1da6a
	REVERSE=""
f1da6a
	rpmlist=($(cat))
f1da6a
f1da6a
	if [ "-r" == "$1" ];
f1da6a
	then
f1da6a
		REVERSE="-r"
f1da6a
	fi
f1da6a
f1da6a
	echo ${rpmlist[@]} | \
f1da6a
	sed -e 's/-/../g' | \
f1da6a
	sort ${REVERSE} -n -t"." -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 -k6,6 -k7,7 \
f1da6a
				 -k8,8 -k9,9 -k10,10 | \
f1da6a
	sed -e 's/\.\./-/g'
f1da6a
}
f1da6a
f1da6a
# read_modules_list:
f1da6a
# Read in a list of modules from standard input. Convert the filenames into
f1da6a
# absolute paths and compute the kernel release for each module (either using
f1da6a
# the modinfo section or through the absolute path.
f1da6a
read_modules_list() {
f1da6a
    local IFS=$'\n'
f1da6a
    modules=($(cat))
f1da6a
f1da6a
    for ((n = 0; n < ${#modules[@]}; n++)); do
f1da6a
        if [ ${modules[n]:0:1} != '/' ]; then
f1da6a
            modules[n]="$PWD/${modules[n]}"
f1da6a
        fi
f1da6a
        if [ -f "${modules[n]}" ]; then
f1da6a
            module_krels[n]=$(krel_of_module ${modules[n]})
f1da6a
        else
f1da6a
            # Try to extract the kernel release from the path
f1da6a
            set -- "${modules[n]#/lib/modules/}"
f1da6a
            module_krels[n]=${1%%/*}
f1da6a
        fi
f1da6a
    done
f1da6a
}
f1da6a
f1da6a
# read_old_initramfs:
f1da6a
compare_initramfs_modules() {
f1da6a
    local old_initramfs=$1
f1da6a
    local new_initramfs=$2
f1da6a
f1da6a
    rm -rf "$tmpdir/old_initramfs"
f1da6a
    rm -rf "$tmpdir/new_initramfs"
f1da6a
    mkdir "$tmpdir/old_initramfs"
f1da6a
    mkdir "$tmpdir/new_initramfs"
f1da6a
f1da6a
    pushd "$tmpdir/old_initramfs" >/dev/null
f1da6a
    zcat "$old_initramfs" | cpio -i 2>/dev/null
f1da6a
    n=0; for i in `find . -iname \*.ko|sort`; do
f1da6a
        old_initramfs_modules[n]="$i"
f1da6a
        n=$((n+1))
f1da6a
    done
f1da6a
    popd >/dev/null
f1da6a
f1da6a
    pushd "$tmpdir/new_initramfs" >/dev/null
f1da6a
    zcat "$new_initramfs" | cpio -i 2>/dev/null
f1da6a
    n=0; for i in `find . -iname \*.ko|sort`; do
f1da6a
        new_initramfs_modules[n]="$i"
f1da6a
        n=$((n+1))
f1da6a
    done
f1da6a
    popd >/dev/null
f1da6a
f1da6a
    if [ "${#old_initramfs_modules[@]}" == "${#new_initramfs_modules[@]}" ];
f1da6a
    then
f1da6a
        for ((n = 0; n < ${#old_initramfs_modules[@]}; n++)); do
f1da6a
            old_md5=`md5sum $tmpdir/old_initramfs/${old_initramfs_modules[n]}|sed -nre 's:(^\ )* .*:\1:p'`
f1da6a
            new_md5=`md5sum $tmpdir/new_initramfs/${new_initramfs_modules[n]}|sed -nre 's:(^\ )* .*:\1:p'`
f1da6a
            if [ ! "$old_md5" == "$new_md5" ];
f1da6a
            then
f1da6a
                return 1
f1da6a
            fi
f1da6a
        done
f1da6a
    else
f1da6a
        return 1
f1da6a
    fi
f1da6a
f1da6a
    return 0
f1da6a
}
f1da6a
f1da6a
# check_initramfs:
f1da6a
# check and possibly also update the initramfs for changed kernels
f1da6a
check_initramfs() {
f1da6a
    local kernel=$1
f1da6a
f1da6a
    # If there is no initramfs already we will not make one here.
f1da6a
    if [ -e "$initramfs_prefix/initramfs-$kernel.img" ];
f1da6a
    then
f1da6a
        old_initramfs="$initramfs_prefix/initramfs-$kernel.img"
f1da6a
        tmp_initramfs="$initramfs_prefix/initramfs-$kernel.tmp"
f1da6a
        new_initramfs="$initramfs_prefix/initramfs-$kernel.img"
f1da6a
f1da6a
        $dracut -f "$tmp_initramfs" "$kernel"
f1da6a
f1da6a
        if ! $(compare_initramfs_modules "$old_initramfs" "$tmp_initramfs");
f1da6a
        then
f1da6a
            doit mv "$tmp_initramfs" "$new_initramfs"
f1da6a
        else
f1da6a
            rm -f "$tmp_initramfs"
f1da6a
        fi
f1da6a
    fi
f1da6a
}
f1da6a
f1da6a
# krel_of_module:
f1da6a
# Compute the kernel release of a module.
f1da6a
krel_of_module() {
f1da6a
    declare module=$1
f1da6a
    /sbin/modinfo -F vermagic "$module" | awk '{print $1}'
f1da6a
}
f1da6a
f1da6a
# module_is_compatible:
f1da6a
# Determine if a module is compatible with a particular kernel release. Also
f1da6a
# include any symbol deps that might be introduced by other external kmods.
f1da6a
module_is_compatible() {
f1da6a
    declare module=$1 krel=$2 module_krel=$(krel_of_module "$module")
f1da6a
f1da6a
    if [ ! -e "$tmpdir/all-symvers-$krel-$module_krel" ]; then
f1da6a
        # Symbols exported by the "new" kernel
f1da6a
        if [ ! -e $tmpdir/symvers-$krel ]; then
f1da6a
            if [ -e /boot/symvers-$krel.gz ]; then
f1da6a
                zcat /boot/symvers-$krel.gz \
f1da6a
                | sed -r -ne 's:^(0x[0]*[0-9a-f]{8}\t[0-9a-zA-Z_]+)\t.*:\1:p'
f1da6a
            fi > $tmpdir/symvers-$krel
f1da6a
        fi
f1da6a
f1da6a
        # Symbols that other add-on modules of the "old" kernel export
f1da6a
        # (and that this module may require)
f1da6a
        if [ ! -e "$tmpdir/extra-symvers-$module_krel" ]; then
f1da6a
            if [ -e /lib/modules/$module_krel/extra ] && \
f1da6a
	       [ -n "`find /lib/modules/$module_krel/extra -type f`" ]; then
f1da6a
                find /lib/modules/$module_krel/extra -name '*.ko' \
f1da6a
                | xargs nm \
f1da6a
                | sed -nre 's:^[0]*([0-9a-f]{8}) A __crc_(.*):0x\1 \2:p'
f1da6a
            fi > $tmpdir/extra-symvers-$module_krel
f1da6a
        fi
f1da6a
f1da6a
        sort -u $tmpdir/symvers-$krel $tmpdir/extra-symvers-$module_krel \
f1da6a
        > "$tmpdir/all-symvers-$krel-$module_krel"
f1da6a
    fi
f1da6a
f1da6a
    # If the module does not have modversions enabled, $tmpdir/modvers
f1da6a
    # will be empty.
f1da6a
    /sbin/modprobe --dump-modversions "$module" \
f1da6a
    | sed -r -e 's:^(0x[0]*[0-9a-f]{8}\t.*):\1:' \
f1da6a
    | sort -u \
f1da6a
    > $tmpdir/modvers
f1da6a
f1da6a
    # Only include lines of the second file in the output that don't
f1da6a
    # match lines in the first file. (The default separator is
f1da6a
    # <space>, so we are matching the whole line.)
f1da6a
    join -j 1 -v 2 $tmpdir/all-symvers-$krel-$module_krel \
f1da6a
                   $tmpdir/modvers > $tmpdir/join
f1da6a
f1da6a
    if [ ! -s $tmpdir/modvers ]; then
f1da6a
        echo "Warning: Module ${module##*/} from kernel $module_krel has no" \
f1da6a
             "modversions, so it cannot be reused for kernel $krel" >&2
f1da6a
    elif [ -s $tmpdir/join ]; then
f1da6a
        [ -n "$verbose" ] &&
f1da6a
        echo "Module ${module##*/} from kernel $module_krel is not compatible" \             "with kernel $krel in symbols:" $(sed -e 's:.* ::' $tmpdir/join)
f1da6a
    else
f1da6a
        [ -n "$verbose" ] &&
f1da6a
        echo "Module ${module##*/} from kernel $module_krel is compatible" \
f1da6a
             "with kernel $krel"
f1da6a
        return 0
f1da6a
    fi
f1da6a
    return 1
f1da6a
}
f1da6a
f1da6a
usage() {
f1da6a
    echo "Usage: ${0##*/} [options] {--add-modules|--remove-modules}"
f1da6a
    echo "${0##*/} [options] {--add-kernel|--remove-kernel} {kernel-release}"
f1da6a
    cat <<'EOF'
f1da6a
--add-modules
f1da6a
        Add a list of modules read from standard input. Create
f1da6a
        symlinks in compatible kernel's weak-updates/ directory.
f1da6a
        The list of modules is read from standard input.
f1da6a
f1da6a
--remove-modules
f1da6a
        Remove compatibility symlinks from weak-updates/ directories
f1da6a
        for a list of modules.  The list of modules is read from
f1da6a
        standard input. Optionally specify --delete-modules to
f1da6a
        prevent weak-modules from attempting to locate any
f1da6a
        compatible modules to replace those being removed.
f1da6a
f1da6a
--add-kernel
f1da6a
        Add compatibility symlinks for all compatible modules to the
f1da6a
        specified or running kernel.
f1da6a
f1da6a
--remove-kernel
f1da6a
        Remove all compatibility symlinks for the specified or current
f1da6a
        kernel.
f1da6a
f1da6a
--no-initramfs
f1da6a
	Do not generate an initramfs.
f1da6a
f1da6a
--verbose
f1da6a
        Print the commands executed.
f1da6a
f1da6a
--dry-run
f1da6a
        Do not create/remove any files.
f1da6a
EOF
f1da6a
    exit $1
f1da6a
}
f1da6a
f1da6a
# module_has_changed:
f1da6a
# Mark if an actual change occured that we need to deal with later by calling
f1da6a
# depmod or mkinitramfs against the affected kernel.
f1da6a
module_has_changed() {
f1da6a
f1da6a
    declare module=$1 krel=$2
f1da6a
f1da6a
    module=${module%.ko}
f1da6a
    module=${module##*/}
f1da6a
f1da6a
    eval "changed_modules_${krel//[^a-zA-Z0-9]/_}=$krel"
f1da6a
    eval "changed_initramfs_${krel//[^a-zA-Z0-9]/_}=$krel"
f1da6a
f1da6a
}
f1da6a
f1da6a
# add_modules:
f1da6a
# Read in a list of modules from stdinput and process them for compatibility
f1da6a
# with installed kernels under /lib/modules.
f1da6a
add_modules() {
f1da6a
    read_modules_list || exit 1
f1da6a
    if [ ${#modules[@]} -gt 0 ]; then
f1da6a
        for krel in $(ls /lib/modules/); do
f1da6a
            [ -e "/boot/symvers-$krel.gz" ] || continue
f1da6a
            for ((n = 0; n < ${#modules[@]}; n++)); do
f1da6a
                module="${modules[n]}"
f1da6a
                module_krel="${module_krels[n]}"
f1da6a
                case "$module" in
f1da6a
                /lib/modules/$krel/*)
f1da6a
                    # Module was built against this kernel, update initramfs.
f1da6a
                    module_has_changed $module $krel
f1da6a
                    continue ;;
f1da6a
                esac
f1da6a
f1da6a
		# Module my also serve as a weak-update built against another
f1da6a
		# kernel. We need to create symlinks for compatible kernels
f1da6a
		# under /lib/modules and rerun depmod/dracut for those.
f1da6a
f1da6a
		subpath=`echo $module | sed -nre "s:/lib/modules/$module_krel/([^/]*)/(.*):\2:p"`
f1da6a
                weak_module="/lib/modules/$krel/weak-updates/${subpath#/}"
f1da6a
                if [ -r "$weak_module" ]; then
f1da6a
                    weak_krel=$(krel_of_module "$weak_module")
f1da6a
                    if [ "$weak_krel" != "$module_krel" ] &&
f1da6a
                       [ "$(printf "%s\n" "$weak_krel" "$module_krel" \
f1da6a
                            | rpmsort | (read input; echo "$input"; \
f1da6a
                                         while read input; do true; done))" = \
f1da6a
                         "$module_krel" ]; then
f1da6a
                        # Keep modules from more recent kernels.
f1da6a
                        [ -n "$verbose" ] && echo \
f1da6a
"Keeping module ${module##*/} from kernel $weak_krel for kernel $krel"
f1da6a
                        continue
f1da6a
                    fi
f1da6a
                fi
f1da6a
                if module_is_compatible $module $krel; then
f1da6a
                    doit mkdir -p $(dirname $weak_module)
f1da6a
                    doit ln -sf $module $weak_module
f1da6a
                    # Module was built against another kernel, update initramfs.
f1da6a
                    module_has_changed $module $krel
f1da6a
                fi
f1da6a
            done
f1da6a
        done
f1da6a
    fi
f1da6a
}
f1da6a
f1da6a
# remove_modules:
f1da6a
# Read in a list of modules from stdinput and process them for removal.
f1da6a
# Parameter is noreplace to delete modules, otherwise link compat.
f1da6a
remove_modules() {
f1da6a
    delete_modules=${1:-replace}
f1da6a
f1da6a
    read_modules_list || exit 1
f1da6a
    if [ ${#modules[@]} -gt 0 ]; then
f1da6a
f1da6a
	# Hunt for all known users of this module in /lib/modules, remove them
f1da6a
	# and create symlinks to other compatible modules (downgrade) if
f1da6a
	# possible, update initramfs for each modified kernel too.
f1da6a
f1da6a
        krels=($(ls /lib/modules/ | rpmsort -r))
f1da6a
        for krel in "${krels[@]}"; do
f1da6a
            [ -e "/boot/symvers-$krel.gz" ] || continue
f1da6a
            for ((n = 0; n < ${#modules[@]}; n++)); do
f1da6a
                module="${modules[n]}"
f1da6a
                module_krel="${module_krels[n]}"
f1da6a
f1da6a
		# Module is going to be removed, update initramfs.
f1da6a
		module_has_changed $module $krel
f1da6a
f1da6a
                subpath="${module#/lib/modules/$module_krel/extra}"
f1da6a
                weak_module="/lib/modules/$krel/weak-updates/${subpath#/}"
f1da6a
                if [ "$module" == "`readlink $weak_module`" ]; then
f1da6a
                    [ -n "$verbose" ] && echo \
f1da6a
"Removing compatible module ${module##*/} from kernel $krel"
f1da6a
                    doit rm -f "$weak_module"
f1da6a
                    if [ "replace" == "$delete_modules" ]; then
f1da6a
                        for krel2 in "${krels[@]}"; do
f1da6a
                            if [ $krel2 != $krel ]; then
f1da6a
                                module="/lib/modules/$krel2/extra/${subpath#/}"
f1da6a
                                [ -e "$module" ] || continue
f1da6a
                                if module_is_compatible "$module" "$krel"; then
f1da6a
                                    [ -n "$verbose" ] && echo \
f1da6a
"Adding compatible module ${module##*/} from kernel $krel2 instead"
f1da6a
                                    doit ln -s "$module" "$weak_module"
f1da6a
			            module_has_changed $module $krel
f1da6a
                                    break
f1da6a
                                fi
f1da6a
                            fi
f1da6a
                        done
f1da6a
                    fi
f1da6a
                    doit rmdir --parents --ignore-fail-on-non-empty \
f1da6a
                               "$(dirname "$weak_module")"
f1da6a
                fi
f1da6a
            done
f1da6a
        done
f1da6a
    fi
f1da6a
}
f1da6a
f1da6a
add_kernel() {
f1da6a
    add_krel=${1:-$(uname -r)}
f1da6a
    if [ ! -e "/boot/symvers-$add_krel.gz" ]; then
f1da6a
        echo "Symvers dump file /boot/symvers-$add_krel.gz" \
f1da6a
             "not found" >&2
f1da6a
        exit 1
f1da6a
    fi
f1da6a
    for krel in $(ls /lib/modules/ | rpmsort -r); do
f1da6a
        [ "$add_krel" = "$krel" ] && continue
f1da6a
        [ -d /lib/modules/$krel/extra ] || continue
f1da6a
        for module in $(find /lib/modules/$krel/extra -name '*.ko'); do
f1da6a
            subpath="${module#/lib/modules/$krel/extra}"
f1da6a
            weak_module="/lib/modules/$add_krel/weak-updates/${subpath#/}"
f1da6a
            [ -e "$weak_module" ] && continue
f1da6a
            if module_is_compatible $module $add_krel; then
f1da6a
		module_has_changed $module $add_krel
f1da6a
                doit mkdir -p $(dirname $weak_module)
f1da6a
                doit ln -sf $module $weak_module
f1da6a
            fi
f1da6a
        done
f1da6a
    done
f1da6a
}
f1da6a
f1da6a
remove_kernel() {
f1da6a
    remove_krel=${1:-$(uname -r)}
f1da6a
    weak_modules="/lib/modules/$remove_krel/weak-updates"
f1da6a
    module_has_changed $weak_modules $remove_krel
f1da6a
    doit rm -rf "$weak_modules"
f1da6a
}
f1da6a
f1da6a
################################################################################
f1da6a
################################## MAIN GUTS ###################################
f1da6a
################################################################################
f1da6a
f1da6a
options=`getopt -o h --long help,add-modules,remove-modules \
f1da6a
                     --long add-kernel,remove-kernel \
f1da6a
                     --long dry-run,no-initramfs,verbose,delete-modules -- "$@"`
f1da6a
f1da6a
[ $? -eq 0 ] || usage 1
f1da6a
f1da6a
eval set -- "$options"
f1da6a
f1da6a
while :; do
f1da6a
    case "$1" in
f1da6a
    --add-modules)
f1da6a
        do_add_modules=1
f1da6a
        ;;
f1da6a
    --remove-modules)
f1da6a
        do_remove_modules=1
f1da6a
        ;;
f1da6a
    --add-kernel)
f1da6a
        do_add_kernel=1
f1da6a
        ;;
f1da6a
    --remove-kernel)
f1da6a
        do_remove_kernel=1
f1da6a
        ;;
f1da6a
    --dry-run)
f1da6a
        dry_run=1
f1da6a
        ;;
f1da6a
    --no-initramfs)
f1da6a
        no_initramfs=1
f1da6a
        ;;
f1da6a
    --verbose)
f1da6a
        verbose=1
f1da6a
        ;;
f1da6a
    --delete-modules)
f1da6a
        do_delete_modules=1
f1da6a
        ;;
f1da6a
    -h|--help)
f1da6a
        usage 0
f1da6a
        ;;
f1da6a
    --)
f1da6a
        shift
f1da6a
        break
f1da6a
        ;;
f1da6a
    esac
f1da6a
    shift
f1da6a
done
f1da6a
f1da6a
if [ -n "$do_add_modules" ]; then
f1da6a
	add_modules
f1da6a
f1da6a
elif [ -n "$do_remove_modules" ]; then
f1da6a
        if [ -n "$do_delete_modules" ]; then
f1da6a
            remove_modules "noreplace"
f1da6a
        else
f1da6a
	    remove_modules
f1da6a
        fi
f1da6a
f1da6a
elif [ -n "$do_add_kernel" ]; then
f1da6a
	kernel=${1:-$(uname -r)}
f1da6a
	add_kernel $kernel
f1da6a
f1da6a
elif [ -n "$do_remove_kernel" ]; then
f1da6a
	kernel=${1:-$(uname -r)}
f1da6a
	remove_kernel $kernel
f1da6a
f1da6a
	exit 0
f1da6a
else
f1da6a
	usage 1
f1da6a
fi
f1da6a
f1da6a
################################################################################
f1da6a
###################### CLEANUP POST ADD/REMOVE MODULE/KERNEL ###################
f1da6a
################################################################################
f1da6a
f1da6a
# run depmod and dracut as needed
f1da6a
for krel in ${!changed_modules_*}; do
f1da6a
    krel=${!krel}
f1da6a
f1da6a
    doit /sbin/depmod -ae -F /boot/System.map-$krel $krel
f1da6a
done
f1da6a
f1da6a
for krel in ${!changed_initramfs_*}; do
f1da6a
    krel=${!krel}
f1da6a
f1da6a
    if [ ! -n "$no_initramfs" ]; then
f1da6a
        check_initramfs $krel
f1da6a
    fi
f1da6a
done