#! /bin/bash # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # Copyright 2006 Andreas Gruenbacher REVISION='$Revision: 1184 $' DATE='$Date: 2006-06-01 13:31:55 +0200 (Thu, 01 Jun 2006) $' REVISION=${REVISION//[^0-9]} DATE=${DATE#*(}; DATE=${DATE%)*} # Define sort order, etc. export LC_ALL=POSIX print_26symvers() { # Module information /sbin/modinfo "$1" \ | sed -re 's,^(filename:[ \t]*)([^/]*/)*,\1,' \ | sed -e 's/^/# /' if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "Failed to extract the module information of $1" >&2 exit 1 fi # Imported symbols declare modversions modversions="$(/sbin/modprobe --dump-modversions "$1")" if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "Failed to determine the imported symbols of $1" >&2 exit 1 fi # Exported symbols ( /usr/bin/nm "$1" \ | sed -nre 's/^(00000000|)([0-9a-f]*) A __crc_(.*)$/0x\2\t+\3/p' [ ${PIPESTATUS[0]} -eq 0 ] || exit 1 echo "$modversions" ) | sort -k2 if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "Failed to determine the exported symbols of $1" >&2 exit 1 fi } get_kernel_release() { set -- $(/sbin/modinfo -F vermagic "$1") case "$1" in 2.6.*-*-*) echo "$1" return 0 ;; *) echo "Failed to determine the kernel version from $1" >&2 exit 1 esac } get_arch() { case "$(file -b "$1")" in "ELF 64-bit LSB relocatable, IA-64"*) echo ia64 ;; "ELF 32-bit LSB relocatable, Intel 80386"*) echo i386 ;; "ELF 32-bit MSB relocatable, PowerPC"*) echo ppc ;; "ELF 64-bit MSB relocatable, cisco 7500"*) echo ppc64 ;; "ELF 32-bit MSB relocatable, IBM S/390"*) echo s390 ;; "ELF 64-bit MSB relocatable, IBM S/390"*) echo s390x ;; "ELF 64-bit LSB relocatable, AMD x86-64"*) echo x86_64 ;; *) echo "Failed to determine the architecture of $1" >&2 exit 1 ;; esac } dump_module() { declare tmpdir=$1 modset=$2 module=$3 rpm_info=$4 declare arch=$(get_arch "$module") krel=$(get_kernel_release "$module") declare dir="$tmpdir/$modset/$arch/$krel" mkdir -p "$dir" if [ -n "$rpm_info" ]; then echo "$rpm_info" > "$dir/.rpminfo" fi if [ -e "$dir/${module##*/}" ]; then echo "Duplicate module ${module#$tmpdir/} for architecture $arch and" \ "kernel release $krel" >&2 exit 1 fi print_26symvers "$module" > "$dir/${module##*/}" } check_modset() { declare dir=$1 res # Open a pipe to the list of defined symbols on file descriptor 3 exec 3< <( sed -nr -e '/^#/d' \ -e 's/^0x0*([0-9a-f]+\t[a-zA-Z0-9_]+).*/\1/p' find $dir -type f \ | xargs sed -nr -e '/^#/d' \ -e 's/^0x0*([0-9a-f]+\t)\+([a-zA-Z0-9_]+).*/\1\2/p' ) # Open a pipe to the list of used symbols on file descriptor 3 exec 4< <( find $dir -type f \ | xargs sed -nr -e '/^#/d' \ -e 's/^0x0*([0-9a-f]+\t)([^\+][a-zA-Z0-9_]+).*/\1\2/p' ) res=$(join -v 2 <(sort -u <&3) <(sort -u <&4)) exec 3<&- 4<&- if [ -n "$res" ]; then echo "Module set ${dir#$tmpdir/} had verification errors for the" "following symbols:" echo "$res" return 1 fi } usage() { echo "\ Usage: ${0##*/} [options] module.ko ... ${0##*/} [options] kmp-package.rpm ... Extract the kernel ABI used and defined by a set of kernel modules from the module binaries, or from kernel modules contained in RPM packages. The information collected is plain text, and contains the module information (see modinfo(1)), and the lists of symbols that the modules use and define, along with the symbol's checksums. The results are stored in gzip-compressed tar archives (one archive per architecture). Modules may define symbols that other modules use. In order for the kernel ABI checks to succeed, all external modules that have interdependencies must be in the same module set: The modules in a set are checked at once; all symbols that are neither provided by the associated kernel package nor by another module in the same set will lead to undefined symbol errors. The kernel ABI can be extracted from modules across compatible machine architectures. The running kernel must not match the modules' kernel. --module-set name Assign a unique name to the specified set of modules. This name should globally and uniquely identify the set of modules (e.g., nvidia-gfx, ati-fglrx). In the case RPM packages are specified, the name of the source RPM package is used as the default module set name. -o file, --output file Override the default output file name. The default ouput name is composed of the module set name, followed by the kernel version, followed by the architecture (e.g., nvidia-gfx-2.6.16-10-i386.tar.gz). -f, --force Overwrite existing output files. By default, ${0##*/} will not overwrite existing files. -q, --quiet Keep quiet; do not print any statistics or status operation. --dry-run Do not actually create or overwrite any files. --version Print the version information and exit. " exit $1 } options=`getopt -o o:fqh --long module-set:,output:,force,quiet,version \ --long dry-run,help \ -- "$@"` [ $? -eq 0 ] || usage 1 eval set -- "$options" while :; do case "$1" in -o|--output) opt_output=$2 shift ;; --module-set) opt_modset=$2 shift ;; -q|--quiet) opt_quiet=1 ;; -f|--force) opt_force=1 ;; --dry-run) opt_dry_run=1 ;; -h|--help) usage 0 ;; --version) echo "$REVISION ($DATE)" exit 0 ;; --) shift break ;; esac shift done [ $# -gt 0 ] || usage tmpdir=$(mktemp -td ${0##*/}.XXXXXX) || exit 1 trap "rm -rf $tmpdir" EXIT shopt -s nullglob for file in "$@"; do if ! [ -e "$file" ]; then echo "$file: File not found" >&2 exit 1 fi case "$(file -b "$file")" in "RPM "*) modset=$opt_modset if [ -z "$modset" ]; then modset=$(rpm -qp --qf '%{SOURCERPM}' "$file" \ | sed -e 's:-[^-]*-[^-]*$::') fi tmpdir2=$(mktemp -td ${0##*/}.XXXXXX) trap "rm -rf $tmpdir $tmpdir2" EXIT rpm_info="$(rpm -qp --qf 'Name: %{NAME}\nVersion: %{VERSION}\nRelease: %{RELEASE}\n[Provides: %{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}|\n]\n[Requires: %{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]\n[%|ENHANCESFLAGS:depflag_strong?{Supplements: %{ENHANCESNAME} %{ENHANCESFLAGS:depflags} %{ENHANCESVERSION}\n}|]\n' "$file" | sed -e 's/ *$//' -e '/^$/d' -e '/^(none)$/d' -e '/^Provides:\|^Requires:\|^Supplements:/!b' -e '/kernel(.*)\|ksym(.*)\|modalias(.*)/!d')" rpm2cpio "$file" | (cd $tmpdir2 && cpio --quiet -dim) \ || exit 1 for module in $(find $tmpdir2 -type f -name '*.ko'); do dump_module "$tmpdir" "$modset" "$module" "$rpm_info" done trap "rm -rf $tmpdir" EXIT rm -rf $tmpdir2 ;; *) if [ -z "$opt_modset" ]; then echo "Please define a module set name using the --module-set" \ "argument" >&2 exit 1 fi dump_module "$tmpdir" "$opt_modset" "$file" ;; esac done # Make sure we have exactly one module set set -- $(find $tmpdir -mindepth 1 -maxdepth 1 -type d -printf '%P\n' | sort -u) if [ $# -eq 0 ]; then echo "Failed to extract anything useful" >&2 exit 1 elif [ $# -ne 1 ]; then echo "Multiple module sets: $*. Please make sure all modules specified" \ "belong to the same set and define the module set name with the" \ "--module-set argument" >&2 exit 1 fi modset=$1 # Make sure we have exactly one kernel version (and allow different flavors) set -- $(printf "%s\n" $tmpdir/*/*/* \ | sed -e 's:.*/::' -e 's:^\([^-]\+-[^-]\+\)-.*$:\1:' \ | sort -u) if [ $# -eq 0 ]; then echo "Failed to extract anything useful" >&2 exit 1 elif [ $# -ne 1 ]; then echo "Multiple kernel versions: $*. Please make sure all modules belong" \ "to the same kernel version" >&2 exit 1 fi kernel_version=$1 if [ -z "$opt_quiet" ]; then echo "Module set: $modset" echo "Kernel version: $kernel_version" fi status=0 linux_obj=/usr/src/linux-$kernel_version-obj for archdir in $tmpdir/*/*; do arch=${archdir##*/} for kreldir in $archdir/*; do flavor=${kreldir##*/} flavor=${flavor#*-*-} if [ -z "$opt_quiet" ]; then set -- $(printf "%s\n" $kreldir/* \ | sed -e 's:.*/::' -e 's:\.ko$::' \ | sort -u) echo "Modules: $* (kernel-$flavor/$arch)" fi # If we find a symvers file for this kernel, verify this set of # modules for symbol mismatch and undefined symbols. if [ -e $linux_obj/$arch/$flavor/Module.symvers ]; then cat $linux_obj/$arch/$flavor/Module.symvers \ | check_modset $kreldir || status=1 continue elif [ -e /boot/symvers-$kernel_version.gz ]; then rpm_arch=$(rpm -qf --qf '%{ARCH}' /boot/symvers-$kernel_version.gz) if [ ${arch//i?86/i386} = ${rpm_arch//i?86/i386} ]; then gzip -cd /boot/symvers-$kernel_version.gz \ | check_modset $kreldir || status=1 continue fi fi if [ -z "$opt_quiet" ]; then echo "Warning: no matching kernel-$flavor or kernel-syms package" \ "installed; skipping optional consistency check." fi done tarball=${opt_output:-$modset-kabi-$kernel_version-$arch.tar.gz} if [ -e "$tarball" -a -z "$opt_force" ]; then echo "Output file $tarball already exists; aborting" >&2 exit 1 fi ( cd $tmpdir find ${archdir#$tmpdir/} -type f \ | tar -cf - --files-from=- \ | gzip -9 \ ) | \ if [ -z "$opt_dry_run" ]; then cat > $tarball || exit 1 else cat > /dev/null fi [ -n "$opt_quiet" ] || echo -e "Wrote $tarball\n" done exit $status # vim:shiftwidth=4 softtabstop=4