#!/bin/bash # # Dynamic Kernel Module Support (DKMS) # Copyright (C) 2003-2008 Dell, Inc. # by Gary Lerhaupt, Matt Domsch, & Mario Limonciello # Copyright (C) 2012 by Darik Horn # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # shopt -s extglob # All of the variables we will accept from dkms.conf. # Does not include directives # The last group of variables has been deprecated readonly dkms_conf_variables="CLEAN PACKAGE_NAME PACKAGE_VERSION POST_ADD POST_BUILD POST_INSTALL POST_REMOVE PRE_BUILD PRE_INSTALL BUILD_DEPENDS BUILD_EXCLUSIVE_ARCH BUILD_EXCLUSIVE_CONFIG BUILD_EXCLUSIVE_KERNEL BUILD_EXCLUSIVE_KERNEL_MIN BUILD_EXCLUSIVE_KERNEL_MAX build_exclude OBSOLETE_BY MAKE MAKE_MATCH PATCH PATCH_MATCH patch_array BUILT_MODULE_NAME built_module_name BUILT_MODULE_LOCATION built_module_location DEST_MODULE_NAME dest_module_name DEST_MODULE_LOCATION dest_module_location STRIP strip AUTOINSTALL NO_WEAK_MODULES SIGN_FILE MOK_SIGNING_KEY MOK_CERTIFICATE REMAKE_INITRD MODULES_CONF MODULES_CONF_OBSOLETES MODULES_CONF_ALIAS_TYPE MODULES_CONF_OBSOLETE_ONLY" # All of the variables not related to signing we will accept from framework.conf. readonly dkms_framework_nonsigning_variables="source_tree dkms_tree install_tree tmp_location verbose symlink_modules autoinstall_all_kernels modprobe_on_install" # All of the signing related variables we will accept from framework.conf. readonly dkms_framework_signing_variables="sign_file mok_signing_key mok_certificate" # Some important regular expressions. Requires bash 3 or above. # Any poor souls still running bash 2 or older really need an upgrade. readonly mv_re='^([^/]*)/(.*)$' # Areas that will vary between Linux and other OS's _get_kernel_dir() { if [[ -z $ksourcedir_fromcli ]]; then KVER=$1 case ${current_os} in Linux) DIR="$install_tree/$KVER/build" ;; GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;; esac echo $DIR else echo $kernel_source_dir fi } _check_kernel_dir() { DIR=$(_get_kernel_dir $1) case ${current_os} in Linux) test -e $DIR/include ;; GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;; *) return 1 ;; esac return $? } # Run a command that we may or may not want to be detailed about. invoke_command() { # $1 = command to be executed using eval. # $2 = Description of command to run # $3 = Redirect command output to this file # $4 = 'background' if you want to run the command asynchronously. local exitval=0 local -r cmd=$([[ $3 ]] && echo "{ $1; } >> $3 2>&1" || echo "$1") [[ $verbose ]] && echo -e "$cmd" || echo -en "$2..." if [[ $4 = background && ! $verbose ]]; then local pid progresspid (eval "$cmd" >/dev/null 2>&1) & pid=$! { on_exit() { kill $(jobs -p) 2>/dev/null wait $(jobs -p) 2>/dev/null } trap on_exit EXIT while /bin/kill --signal 0 $pid > /dev/null 2>&1; do sleep 3 & wait $! echo -en "." done } & progresspid=$! wait $pid 2>/dev/null exitval=$? kill $progresspid 2>/dev/null wait $progresspid 2>/dev/null else eval "$cmd"; exitval=$? fi if (($exitval > 0)); then echo -en "(bad exit status: $exitval)" # Print the failing command without the clunky redirection [[ ! $verbose ]] && echo -en "\nFailed command:\n$1" fi echo -en "\n" return $exitval } error() ( exec >&2 echo -n $"Error! " for s in "$@"; do echo "$s"; done ) warn() ( exec >&2 echo -n $"Warning: " for s in "$@"; do echo "$s"; done ) deprecated() ( exec >&2 echo -n $"Deprecated feature: " for s in "$@"; do echo "$s"; done ) # Print an error message and die with the passed error code. die() { # $1 = error code to return with # rest = strings to print before we exit. ret=$1 shift error "$@" [[ $die_is_fatal = yes ]] && exit $ret || return $ret } # Print a warning message and die with the passed error code. diewarn() { # $1 = error code to return with # rest = strings to print before we exit. ret=$1 shift warn "$@" [[ $die_is_fatal = yes ]] && exit $ret || return $ret } mktemp_or_die() { local t t=$(mktemp "$@") && echo "$t" && return [[ $* = *-d* ]] && die 1 $"Unable to make temporary directory" die 1 "Unable to make temporary file." } show_usage() { echo $"Usage: $0 [action] [options]" echo $" [action] = { add | remove | build | unbuild | install | uninstall | match |" echo $" autoinstall | mktarball | ldtarball | status }" echo $" [options] = [-m module] [-v module-version] [-k kernel-version] [-a arch]" echo $" [-c dkms.conf-location] [-q] [--force] [--force-version-override] [--all]" echo $" [--templatekernel=kernel] [--directive='cli-directive=cli-value']" echo $" [--config=kernel-.config-location] [--archive=tarball-location]" echo $" [--kernelsourcedir=source-location] [--rpm_safe_upgrade]" echo $" [--dkmstree path] [--sourcetree path] [--installtree path]" echo $" [--binaries-only] [--source-only] [--verbose]" echo $" [--no-depmod] [--modprobe-on-install] [-j number] [--version]" } VER() { # $1 = kernel version string # Pad all numbers in $1 so that they have at least three digits, e.g., # 2.6.9-1cvs200409091247 => 002.006.009-001cvs200409091247 # The result should compare correctly as a string. echo $1 | sed -e 's:\([^0-9]\)\([0-9]\):\1 \2:g' \ -e 's:\([0-9]\)\([^0-9]\):\1 \2:g' \ -e 's:\(.*\): \1 :' \ -e 's: \([0-9]\) : 00\1 :g' \ -e 's: \([0-9][0-9]\) : 0\1 :g' \ -e 's: ::g' } # Find out how many CPUs there are so that we may pass an appropriate -j # option to make. Ignore hyperthreading for now. get_num_cpus() { # use nproc(1) from coreutils 8.1-1+ if available, otherwise single job if [[ -x /usr/bin/nproc ]]; then nproc else echo "1" fi } # Finds a .ko or .ko.xz based on a directory and module name # must call set_module_suffix first compressed_or_uncompressed() { # module dir = $1 # module = $2 local test1="$1/$2$module_uncompressed_suffix" local test2="$1/$2$module_uncompressed_suffix$module_compressed_suffix" if [[ -e "$test1" ]]; then echo "$test1" elif [[ -e "$test2" ]]; then echo "$test2" fi } # Finds .ko or .ko.xz based on a tree and module name # must call set_module_suffix first find_module() { # tree = $1 # module = $2 find "$1" -name "$2$module_uncompressed_suffix" -o -name "$2$module_suffix" -type f return $? } # Figure out the correct module suffix for the kernel we are currently # dealing with, which may or may not be the currently installed kernel. set_module_suffix() { # $1 = the kernel to base the module_suffix on kernel_test="${1:-$(uname -r)}" module_uncompressed_suffix=".ko" grep -q '\.gz:' $install_tree/$kernel_test/modules.dep 2>/dev/null && module_compressed_suffix=".gz" grep -q '\.xz:' $install_tree/$kernel_test/modules.dep 2>/dev/null && module_compressed_suffix=".xz" grep -q '\.zst:' $install_tree/$kernel_test/modules.dep 2>/dev/null && module_compressed_suffix=".zst" module_suffix="$module_uncompressed_suffix$module_compressed_suffix" } set_kernel_source_dir_and_kconfig() { if [[ -z "${ksourcedir_fromcli}" ]]; then # $1 = the kernel to base the directory on kernel_source_dir="$(_get_kernel_dir "$1")" fi if [[ -z "${kconfig_fromcli}" ]]; then kernel_config="${kernel_source_dir}/.config" fi } check_all_is_banned() { if [[ $all ]]; then die 5 $"The action $1 does not support the --all parameter." fi } # A little test function for DKMS commands that only work on one kernel. have_one_kernel() { if (( ${#kernelver[@]} != 1 )); then die 4 $"The action $1 does not support multiple kernel version parameters on the command line." fi check_all_is_banned $1 } # Set up the kernelver and arch arrays. You must have a 1:1 correspondence -- # if there is an entry in kernelver[$i], there must also be an entry in arch[$i] # Note the special casing for the status action -- the status functions just # report on what we already have, and will break with the preprocessing that # this function provides. setup_kernels_arches() { # If all is set, use dkms status to fill the arrays if [[ $all && $1 != status ]]; then local i=0 while read line; do line=${line#*/}; line=${line#*/}; # (I would leave out the delimiters in the status output # in the first place.) kernelver[$i]=${line%/*} arch[$i]=${line#*/} i=$(($i + 1)) done < <(module_status_built "$module" "$module_version" | sort -V) fi # Set default kernel version and arch, if none set (but only --all isn't set) if [[ $1 != status ]]; then if [[ ! $kernelver && ! $all ]]; then kernelver[0]=$(uname -r) fi if [[ ! $arch ]]; then kernelver_rpm=$(rpm -qf "$install_tree/$kernelver" 2>/dev/null | \ grep -v "not owned by any package" | grep kernel | head -n 1) if ! arch[0]=$(rpm -q --queryformat "%{ARCH}" "$kernelver_rpm" 2>/dev/null); then arch[0]=$(uname -m) if [[ $arch = x86_64 ]] && grep -q Intel /proc/cpuinfo && ls $install_tree/$kernelver/build/configs 2>/dev/null | grep -q "ia32e"; then arch[0]="ia32e" fi fi fi if [[ ! $arch ]]; then die 12 $"Could not determine architecture." fi fi # If only one arch is specified, make it so for all the kernels if ((${#arch[@]} == 1 && ${#kernelver[@]} > 1)); then while ((${#arch[@]} < ${#kernelver[@]})); do arch[${#arch[@]}]=$arch done fi # Set global multi_arch multi_arch="" local i=0 for ((i=0; $i < ${#arch[@]}; i++)); do [[ $arch != ${arch[$i]} ]] && { multi_arch="true" break } done } do_depmod() { if [[ $no_depmod ]]; then return fi # $1 = kernel version if [[ ${current_os} != Linux ]] ; then return fi if [[ ! -f $install_tree/$1/modules.dep ]]; then # if the corresponding linux image $1 is not installed # do not create modules.dep echo "Skipping depmod because '$install_tree/$1/modules.dep' is missing." return fi if [[ -f /boot/System.map-$1 ]]; then depmod -a "$1" -F "/boot/System.map-$1" else depmod -a "$1" fi if [[ -f $install_tree/$1/modules.dep && ! -s $install_tree/$1/modules.dep ]]; then # if modules.dep is empty, we just removed the last kernel module from # no longer installed kernel $1, so do not leave stale depmod files around rm -fv $install_tree/$1/modules.{alias,dep,devname,softdep,symbols,*.bin} rmdir --ignore-fail-on-non-empty $install_tree/$1 [[ -d $install_tree/$1 ]] || echo $"removed directory $install_tree/$1" fi } # Grab distro information from os-release. distro_version() { for f in /etc/os-release /usr/lib/os-release; do if [[ -e $f ]]; then ( . "$f" if [[ "$ID" = "ubuntu" ]]; then # ID_LIKE=debian in ubuntu echo $ID elif [[ ${#ID_LIKE[@]} != 0 ]]; then echo ${ID_LIKE[0]} else echo $ID fi ) return fi done die 4 $"System is missing os-release file." } override_dest_module_location() { local orig_location="$1" [[ ${addon_modules_dir} ]] && echo "/${addon_modules_dir}" && return if [[ $current_os = GNU/kFreeBSD ]] ; then # Does not support subdirs, regardless of distribution echo "" && return fi case "$running_distribution" in fedora* | rhel* | ovm*) echo "/extra" && return ;; sles* | suse* | opensuse*) echo "/updates" && return ;; debian* | ubuntu*) echo "/updates/dkms" && return ;; arch*) echo "/updates/dkms" && return ;; *) ;; esac echo "$orig_location" } # Source a file safely. # We want to ensure that the .conf file we source does not stomp all over # parts of the environment we don't want them to. This makes it so that # it is harder to accidentally corrupt our environment. conf files can # still deliberately trash the environment by abusing dkms_directive env # variables or by crafting special values that will make eval do evil things. safe_source() { # $1 = file to source # $@ = environment variables to echo out local to_source_file="$1"; shift declare -a -r export_envs=("$@") local tmpfile=$(mktemp_or_die) ( exec >"$tmpfile" . "$to_source_file" >/dev/null # This is really ugly, but a neat hack # Remember, in bash 2.0 and greater all variables are really arrays. for _export_env in "${export_envs[@]}"; do for _i in $(eval echo \${!$_export_env[@]}); do eval echo '$_export_env[$_i]=\"${'$_export_env'[$_i]}\"' done done # handle DKMS_DIRECTIVE stuff specially. for directive in $(set | grep ^DKMS_DIRECTIVE | cut -d = -f 2-3); do directive_name=${directive%%=*} directive_value=${directive#*=} echo "$directive_name=\"$directive_value\"" done ) . "$tmpfile" rm "$tmpfile" (( ${#REMAKE_INITRD[@]} )) && deprecated "REMAKE_INITRD ($to_source_file)" (( ${#MODULES_CONF[@]} )) && deprecated "MODULES_CONF ($to_source_file)" (( ${#MODULES_CONF_OBSOLETES[@]} )) && deprecated "MODULES_CONF_OBSOLETES ($to_source_file)" (( ${#MODULES_CONF_ALIAS_TYPE[@]} )) && deprecated "MODULES_CONF_ALIAS_TYPE ($to_source_file)" (( ${#MODULES_CONF_OBSOLETE_ONLY[@]} )) && deprecated "MODULES_CONF_OBSOLETE_ONLY ($to_source_file)" } # Source a dkms.conf file and perform appropriate postprocessing on it. # Do our best to not repeatedly source the same .conf file -- this can happen # when chaining module installation functions or autoinstalling. read_conf() { # $1 kernel version (required) # $2 arch (required) # $3 dkms.conf location (optional) local return_value=0 local read_conf_file="$dkms_tree/$module/$module_version/source/dkms.conf" # Set variables supported in dkms.conf files (eg. $kernelver) local kernelver="$1" local arch="$2" set_kernel_source_dir_and_kconfig "$1" # Find which conf file to check [[ $conf ]] && read_conf_file="$conf" [[ $3 ]] && read_conf_file="$3" [[ -r $read_conf_file ]] || die 4 $"Could not locate dkms.conf file." \ $"File: $read_conf_file does not exist." [[ $last_mvka = $module/$module_version/$1/$2 && \ $last_mvka_conf = $(readlink -f $read_conf_file) ]] && return # Clear variables and arrays for var in $dkms_conf_variables; do unset $var done # Source in the dkms.conf. # Allow for user-specified overrides in order of specificity. local _conf_file for _conf_file in "$read_conf_file" "/etc/dkms/$module.conf" \ "/etc/dkms/$module-$module_version.conf" "/etc/dkms/$module-$module_version-$1.conf" \ "/etc/dkms/$module-$module_version-$1-$2.conf"; do [[ -e $_conf_file ]] && safe_source "$_conf_file" $dkms_conf_variables done # Source in the directive_array for directive in "${directive_array[@]}"; do directive_name=${directive%%=*} directive_value=${directive#*=} export $directive_name="$directive_value" echo $"DIRECTIVE: $directive_name=\"$directive_value\"" done # Set variables clean="$CLEAN" package_name="$PACKAGE_NAME" package_version="$PACKAGE_VERSION" post_add="$POST_ADD" post_build="$POST_BUILD" post_install="$POST_INSTALL" post_remove="$POST_REMOVE" pre_build="$PRE_BUILD" pre_install="$PRE_INSTALL" obsolete_by="$OBSOLETE_BY" # Fail if no PACKAGE_NAME if [[ ! $package_name ]]; then echo $"dkms.conf: Error! No 'PACKAGE_NAME' directive specified.">&2 return_value=1 fi # Fail if no PACKAGE_VERSION if [[ ! $package_version ]]; then echo $"dkms.conf: Error! No 'PACKAGE_VERSION' directive specified.">&2 return_value=1 fi # Set module naming/location arrays local index array_size=0 s for s in ${#BUILT_MODULE_NAME[@]} \ ${#BUILT_MODULE_LOCATION[@]} \ ${#DEST_MODULE_NAME[@]} \ ${#DEST_MODULE_LOCATION[@]}; do ((s > array_size)) && array_size=$s done for ((index=0; index < array_size; index++)); do # Set values built_module_name[$index]=${BUILT_MODULE_NAME[$index]} built_module_location[$index]=${BUILT_MODULE_LOCATION[$index]} dest_module_name[$index]=${DEST_MODULE_NAME[$index]} dest_module_location[$index]=${DEST_MODULE_LOCATION[$index]} case ${STRIP[$index]} in [nN]*) strip[$index]="no" ;; [yY]*) strip[$index]="yes" ;; '') strip[$index]=${strip[0]:-yes} ;; esac # If unset, set by defaults [[ ! ${built_module_name[$index]} ]] && \ ((array_size == 1)) && \ built_module_name[$index]=$PACKAGE_NAME [[ ! ${dest_module_name[$index]} ]] && \ dest_module_name[$index]=${built_module_name[$index]} [[ ${built_module_location[$index]} && \ ${built_module_location[$index]:(-1)} != / ]] && \ built_module_location[$index]="${built_module_location[$index]}/" # FAIL if no built_module_name if [[ ! ${built_module_name[$index]} ]]; then echo $"dkms.conf: Error! No 'BUILT_MODULE_NAME' directive specified for record #$index." >&2 return_value=1 fi # FAIL if built_module_name ends in .o or .ko case ${built_module_name[$index]} in *.o|*.ko) echo $"dkms.conf: Error! 'BUILT_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2 return_value=1 ;; esac # FAIL if dest_module_name ends in .o or .ko case ${dest_module_name[$index]} in *.o|*.ko) echo $"dkms.conf: Error! 'DEST_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2 return_value=1 ;; esac # Override location for specific distributions dest_module_location[$index]="$(override_dest_module_location ${dest_module_location[$index]})" # Fail if no DEST_MODULE_LOCATION if [[ ! ${DEST_MODULE_LOCATION[$index]} ]]; then echo $"dkms.conf: Error! No 'DEST_MODULE_LOCATION' directive specified for record #$index.">&2 return_value=1 fi # Fail if bad DEST_MODULE_LOCATION case ${DEST_MODULE_LOCATION[$index]} in /kernel*) ;; /updates*) ;; /extra*) ;; *) echo $"dkms.conf: Error! Directive 'DEST_MODULE_LOCATION' does not begin with">&2 echo $"'/kernel', '/updates', or '/extra' in record #$index.">&2 return_value=1 ;; esac done # Warn if no modules are specified if ((array_size == 0)); then echo $"dkms.conf: Warning! Zero modules specified." >&2 fi # Get the correct make command [[ ${MAKE_MATCH[0]} ]] || make_command="${MAKE[0]}" for ((index=0; index < ${#MAKE[@]}; index++)); do [[ ${MAKE[$index]} && ${MAKE_MATCH[$index]} && \ $1 =~ ${MAKE_MATCH[$index]} ]] && \ make_command="${MAKE[$index]}" done # Use the generic make and make clean commands if not specified [[ ! $make_command ]] && make_command="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build" [[ ! $clean ]] && clean="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build clean" # Check if clang was used to compile or lld was used to link the kernel. if [[ -e $kernel_source_dir/vmlinux ]]; then if readelf -p .comment $kernel_source_dir/vmlinux | grep -q clang; then make_command="${make_command} CC=clang" fi if readelf -p .comment $kernel_source_dir/vmlinux | grep -q LLD; then make_command="${make_command} LD=ld.lld" fi elif [[ -e "${kernel_config}" ]]; then if grep -q CONFIG_CC_IS_CLANG=y "${kernel_config}"; then make_command="${make_command} CC=clang" fi if grep -q CONFIG_LD_IS_LLD=y "${kernel_config}"; then make_command="${make_command} LD=ld.lld" fi fi # Set patch_array (including kernel specific patches) count=0 for ((index=0; index < ${#PATCH[@]}; index++)); do if [[ ${PATCH[$index]} && (! ${PATCH_MATCH[$index]} || $1 =~ ${PATCH_MATCH[$index]}) ]]; then patch_array[$count]="${PATCH[$index]}" count=$(($count+1)) fi done # Set build_exclude [[ $BUILD_EXCLUSIVE_KERNEL && ! $1 =~ $BUILD_EXCLUSIVE_KERNEL ]] && build_exclude="yes" [[ $BUILD_EXCLUSIVE_KERNEL_MIN && "$(VER "$1")" < "$(VER "$BUILD_EXCLUSIVE_KERNEL_MIN")" ]] && build_exclude="yes" [[ $BUILD_EXCLUSIVE_KERNEL_MAX && "$(VER "$1")" > "$(VER "$BUILD_EXCLUSIVE_KERNEL_MAX")" ]] && build_exclude="yes" [[ $BUILD_EXCLUSIVE_ARCH && ! $2 =~ $BUILD_EXCLUSIVE_ARCH ]] && build_exclude="yes" if [[ $BUILD_EXCLUSIVE_CONFIG && -e "${kernel_config}" ]]; then local kconf for kconf in $BUILD_EXCLUSIVE_CONFIG ; do case "$kconf" in !*) grep -q "^${kconf#!}=[ym]" "${kernel_config}" && build_exclude="yes" ;; *) grep -q "^${kconf}=[ym]" "${kernel_config}" || build_exclude="yes" ;; esac done fi # Set clean [[ $clean ]] || clean="make clean" ((return_value == 0)) && last_mvka="$module/$module_version/$1/$2" && last_mvka_conf="$(readlink -f "$read_conf_file")" return $return_value } # Source specified variables from dkms framework configuration files. read_framework_conf() { for i in /etc/dkms/framework.conf /etc/dkms/framework.conf.d/*.conf; do [[ -e "$i" ]] && safe_source "$i" "$@" done } # Little helper function for parsing the output of modinfo. get_module_verinfo(){ local ver local srcver local checksum local vals= while read -a vals; do case "${vals[0]}" in version:) ver="${vals[1]}" checksum="${vals[2]}" ;; srcversion:) srcver="${vals[1]}" ;; esac done < <(modinfo "$1") echo -E "${ver}" # Use obsolete checksum info if srcversion is not available echo -E "${srcver:-$checksum}" } # Compare two modules' version # Output: # "==": They are the same version and the same srcversion # "=": They are the same version, but not the same srcversion # ">": 1st one is newer than 2nd one # "<": 1st one is older than 2nd one # "?": Cannot determine # Returns 0 if same version, otherwise 1 compare_module_version() { readarray -t ver1 <<< "$(get_module_verinfo "$1")" readarray -t ver2 <<< "$(get_module_verinfo "$2")" if [[ "${ver1[0]}" = "${ver2[0]}" ]]; then if [[ "${ver1[1]}" = "${ver2[1]}" ]]; then echo "==" else echo "=" fi return 0 elif [[ ! "$ver1" ]] || [[ ! "$ver2" ]]; then echo "?" elif [[ "$(VER "${ver1[0]}")" > "$(VER "${ver2[0]}")" ]]; then echo ">" else echo "<" fi return 1 } # Perform some module version sanity checking whenever we are installing # modules. check_version_sanity() { # $1 = kernel_version # $2 = arch # $3 = obs by kernel version # $4 = dest_module_name local lib_tree="$install_tree/$1" res= echo $"Running module version sanity check." local i=0 if [[ -n $3 ]]; then # Magic split into array syntax saves trivial awk and cut calls. local -a obs=(${3//-/ }) local -a my=(${1//-/ }) local obsolete=0 if [[ ${obs} && ${my} ]]; then if [[ $(VER ${obs}) == $(VER ${my}) && ! $force ]]; then # They get obsoleted possibly in this kernel release if [[ ! ${obs[1]} ]]; then # They were obsoleted in this upstream kernel obsolete=1 elif [[ $(VER ${my[1]}) > $(VER ${obs[1]}) ]]; then # They were obsoleted in an earlier ABI bump of the kernel obsolete=1 elif [[ $(VER ${my[1]}) = $(VER ${obs[1]}) ]]; then # They were obsoleted in this ABI bump of the kernel obsolete=1 fi elif [[ $(VER ${my}) > $(VER ${obs}) && ! $force ]]; then # They were obsoleted in an earlier kernel release obsolete=1 fi fi if ((obsolete == 1)); then echo $"" >&2 echo $"Module has been obsoleted due to being included" >&2 echo $"in kernel $3. We will avoid installing" >&2 echo $"for future kernels above $3." >&2 echo $"You may override by specifying --force." >&2 return 1 fi fi set_module_suffix "$1" read -a kernels_module < <(find_module "$lib_tree" "${4}") [[ -z $kernels_module ]] && return 0 if [[ "$force_version_override" == "true" ]]; then # Skip the following version checking code. return 0 fi if [[ ${kernels_module[1]} ]]; then warn $"Warning! Cannot do version sanity checking because multiple ${4}$module_suffix" \ $"modules were found in kernel $1." return 0 fi local dkms_module=$(compressed_or_uncompressed "$dkms_tree/$module/$module_version/$1/$2/module/" "${4}") local cmp_res="$(compare_module_version "${kernels_module}" "${dkms_module}")" if [[ "${cmp_res}" = ">" ]]; then if [[ ! "$force" ]]; then error $"Module version $(get_module_verinfo "${dkms_module}" | head -n 1) for $4${module_suffix}" \ $"is not newer than what is already found in kernel $1 ($(get_module_verinfo "${kernels_module}" | head -n 1))." \ $"You may override by specifying --force." return 1 fi elif [[ "${cmp_res}" = "==" ]]; then if [[ ! "$force" ]]; then # if the module has neither version nor srcversion/checksum, check the binary files instead local verinfo="$(get_module_verinfo "${dkms_module}")" if [[ "$(echo "$verinfo" | tr -d '[:space:]')" ]] || diff "${kernels_module}" "${dkms_module}" &>/dev/null; then echo $"Module version $(echo "$verinfo" | head -n 1) for $4${module_suffix}" >&2 echo $"exactly matches what is already found in kernel $1." >&2 echo $"DKMS will not replace this module." >&2 echo $"You may override by specifying --force." >&2 return 1 fi fi fi return 0 } check_module_args() { [[ $module && $module_version ]] && return die 1 $"Arguments and are not specified." \ $"Usage: $1 / or" \ $" $1 -m / or" \ $" $1 -m -v " } read_conf_or_die() { read_conf "$@" && return die 8 $"Bad conf file."\ $"File: ${3:-$conf} does not represent a valid dkms.conf file." } run_build_script() { # $1 = script type # $2 = script to run local script_type run [[ $2 ]] || return 0 case "$1" in pre_build|post_build) script_type='build' ;; *) script_type='source' ;; esac run="$dkms_tree/$module/$module_version/$script_type/$2" if [[ -x ${run%% *} ]]; then echo $"" echo $"Running the $1 script:" ( cd "$dkms_tree/$module/$module_version/$script_type/" exec $run ) else echo $"" warn $"The $1 script is not executable." fi } # Register a DKMS-ified source tree with DKMS. # This function is smart enough to register the module if we # passed a source tree or a tarball instead of relying on the source tree # being unpacked into /usr/src/$module-$module_version. add_module() { # If $archive is set and $module and $module_version are not, # try loading the tarball passed first. if [[ $archive_location && ! $module && ! $module_version ]]; then load_tarball elif [[ $try_source_tree && ! $module && ! $module_version ]]; then add_source_tree "$try_source_tree" fi # Check that we have all the arguments check_module_args add # Do stuff for --rpm_safe_upgrade if [[ $rpm_safe_upgrade ]]; then local pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status) local lock_name=$(mktemp_or_die $tmp_location/dkms_rpm_safe_upgrade_lock.$pppid.XXXXXX) echo "$module-$module_version" >> $lock_name ps -o lstart --no-headers -p $pppid 2>/dev/null >> $lock_name fi # Check that this module-version hasn't already been added if is_module_added "$module" "$module_version"; then die 3 $"DKMS tree already contains: $module-$module_version" \ $"You cannot add the same module/version combo more than once." fi [[ $conf ]] || conf="$source_tree/$module-$module_version/dkms.conf" # Check that /usr/src/$module-$module_version exists if ! [[ -d $source_tree/$module-$module_version ]]; then die 2 $"Could not find module source directory." \ $"Directory: $source_tree/$module-$module_version does not exist." fi # Check the conf file for sanity read_conf_or_die "$kernelver" "$arch" "$conf" # Create the necessary dkms tree structure echo $"Creating symlink $dkms_tree/$module/$module_version/source -> $source_tree/$module-$module_version" mkdir -p "$dkms_tree/$module/$module_version/build" ln -s "$source_tree/$module-$module_version" "$dkms_tree/$module/$module_version/source" # Run the post_add script run_build_script post_add "$post_add" } # Prepare a kernel source or include tree for compiling a module. # Most modern-ish distros do not require this function at all, # so it will be removed in a future release. prepare_kernel() { # $1 = kernel version to prepare # $2 = arch to prepare set_kernel_source_dir_and_kconfig "$1" # Check that kernel-source exists _check_kernel_dir "$1" || { die 1 $"Your kernel headers for kernel $1 cannot be found at $install_tree/$1/build or $install_tree/$1/source." \ $"Please install the linux-headers-$1 package or use the --kernelsourcedir option to tell DKMS where it's located." } } prepare_signing() { do_signing=0 if [[ ! -f ${kernel_config} ]]; then echo "Kernel config ${kernel_config} not found, modules won't be signed" return fi if ! grep -q "^CONFIG_MODULE_SIG_HASH=" "${kernel_config}"; then echo "The kernel is built without module signing facility, modules won't be signed" return fi sign_hash=$(grep "^CONFIG_MODULE_SIG_HASH=" "${kernel_config}" | cut -f2 -d= | sed 's/"//g') # Lazy source in signing related configuration read_framework_conf $dkms_framework_signing_variables if [[ ! ${sign_file} ]]; then case "$running_distribution" in debian* ) sign_file="/usr/lib/linux-kbuild-${kernelver%.*}/scripts/sign-file" ;; ubuntu* ) sign_file="$(command -v kmodsign)" if [[ ! -x "${sign_file}" ]]; then sign_file="/usr/src/linux-headers-$kernelver/scripts/sign-file" fi ;; esac if [[ ! -f ${sign_file} ]]; then sign_file="$install_tree/$kernelver/build/scripts/sign-file" fi fi echo "Sign command: $sign_file" if [[ ! -f ${sign_file} || ! -x ${sign_file} ]]; then echo "Binary ${sign_file} not found, modules won't be signed" return fi if [[ -z "${mok_signing_key}" ]]; then # No custom key specified, use the default key created by update-secureboot-policy for Ubuntu # Debian's update-secureboot-policy has no --new-key option case "$running_distribution" in ubuntu* ) mok_signing_key="/var/lib/shim-signed/mok/MOK.priv" mok_certificate="/var/lib/shim-signed/mok/MOK.der" if [[ ! -f ${mok_signing_key} || ! -f ${mok_certificate} ]]; then if [[ ! -x "$(command -v update-secureboot-policy)" ]]; then echo "Binary update-secureboot-policy not found, modules won't be signed" return fi # update-secureboot-policy won't create new key if $mok_certificate exists if [[ -f ${mok_certificate} ]]; then rm -f "${mok_certificate}" fi echo "Certificate or key are missing, generating them using update-secureboot-policy..." SHIM_NOTRIGGER=y update-secureboot-policy --new-key &>/dev/null update-secureboot-policy --enroll-key fi ;; esac fi if [[ ! ${mok_signing_key} ]]; then mok_signing_key="/var/lib/dkms/mok.key" fi echo "Signing key: $mok_signing_key" if [[ ! ${mok_certificate} ]]; then mok_certificate="/var/lib/dkms/mok.pub" fi echo "Public certificate (MOK): $mok_certificate" # scripts/sign-file.c in kernel source also supports using "pkcs11:..." as private key if [[ $mok_signing_key != "pkcs11:"* ]] && ( [[ ! -f $mok_signing_key || ! -f $mok_certificate ]] ); then echo "Certificate or key are missing, generating self signed certificate for MOK..." if ! command -v openssl >/dev/null; then echo "openssl not found, can't generate key and certificate." return fi openssl req -new -x509 -nodes -days 36500 -subj "/CN=DKMS module signing key" \ -newkey rsa:2048 -keyout "$mok_signing_key" \ -outform DER -out "$mok_certificate" > /dev/null 2>&1 if [[ ! -f ${mok_signing_key} ]]; then echo "Key file ${mok_signing_key} not found and can't be generated, modules won't be signed" return fi fi if [[ ! -f ${mok_certificate} ]]; then echo "Certificate file ${mok_certificate} not found and can't be generated, modules won't be signed" return fi do_signing=1 } # Get ready to build a module that has been registered with DKMS. prepare_build() { # If the module has not been added, try to add it. is_module_added "$module" "$module_version" || add_module local -r base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch" local -r build_dir="$dkms_tree/$module/$module_version/build" local -r source_dir="$dkms_tree/$module/$module_version/source" # Check that the module has not already been built for this kernel [[ -d $base_dir ]] && die 3 \ $"This module/version has already been built on: $kernelver" \ $"Directory $base_dir already exists. Use the dkms remove function before trying to build again." # Read the conf file set_module_suffix "$kernelver" read_conf_or_die "$kernelver" "$arch" # Error out if build_exclude is set [[ $build_exclude ]] && diewarn 77 \ $"The $base_dir/dkms.conf"\ $"for module $module includes a BUILD_EXCLUSIVE directive"\ $"which does not match this kernel/arch/config."\ $"This indicates that it should not be built." # Error out if source_tree is basically empty (binary-only dkms tarball w/ --force check) (($(ls $source_dir | wc -l | awk {'print $1'}) < 2)) && die 8 \ $"The directory $source_dir does not appear to have module source located within it."\ $"Build halted." # Set up temporary build directory for build rm -rf "$build_dir" cp -a "$source_dir/" "$build_dir" cd "$build_dir" # Apply any patches for p in "${patch_array[@]}"; do [[ ! -e $build_dir/patches/$p ]] && \ report_build_problem 5 \ $" Patch $p as specified in dkms.conf cannot be" \ $"found in $build_dir/patches/." invoke_command "patch -p1 < ./patches/$p" "applying patch $p" || \ report_build_problem 6 $"Application of patch $p failed." \ $"Check $build_dir for more information." done if [[ -f $kernel_source_dir/.kernelvariables ]]; then export CC=$(echo -e "show-%:\n\t@echo \$(\$*)\ninclude $kernel_source_dir/.kernelvariables" | make -f - show-CC) else unset CC fi if [[ -e "${kernel_config}" ]]; then local cc=$(sed -n 's|^CONFIG_CC_VERSION_TEXT="\([^ ]*\) .*"|\1|p' "${kernel_config}") if command -v "$cc" >/dev/null; then export CC="$cc" export KERNEL_CC="$cc" fi if grep -q 'CONFIG_CC_IS_CLANG=y' "${kernel_config}"; then local cc=clang if command -v "$cc" >/dev/null; then export CC="$cc" export KERNEL_CC="$cc" fi fi if grep -q 'CONFIG_LD_IS_LLD=y' "${kernel_config}"; then local ld=ld.lld if command -v "$ld" >/dev/null; then export LD="$ld" export KERNEL_LD="$ld" fi fi fi # Run the pre_build script run_build_script pre_build "$pre_build" } # Build our previously prepared source tree. prepare_build must be called # before calling this function. actual_build() { local -r base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch" local -r build_dir="$dkms_tree/$module/$module_version/build" local -r build_log="$build_dir/make.log" echo $"" echo $"Building module:" invoke_command "$clean" "Cleaning build area" '' background echo $"DKMS make.log for $module-$module_version for kernel $kernelver ($arch)" >> "$build_log" date >> "$build_log" local the_make_command="${make_command/#make/make -j$parallel_jobs KERNELRELEASE=$kernelver}" invoke_command "$the_make_command" "Building module(s)" "$build_log" background || \ report_build_problem 10 $"Bad return status for module build on kernel: $kernelver ($arch)" \ $"Consult $build_log for more information." # Make sure all the modules built successfully for ((count=0; count < ${#built_module_name[@]}; count++)); do [[ -e ${built_module_location[$count]}${built_module_name[$count]}$module_uncompressed_suffix ]] && continue report_build_problem 7 \ $" Build of ${built_module_name[$count]}$module_uncompressed_suffix failed for: $kernelver ($arch)" \ $"Make sure the name of the generated module is correct and at the root of the" \ $"build directory, or consult make.log in the build directory" \ $"$build_dir for more information." done cd - >/dev/null # Build success, so create DKMS structure for a built module mkdir -p "$base_dir/log" [[ $kernel_config ]] && cp -f "$kernel_config" "$base_dir/log/" mv -f "$build_log" "$base_dir/log/make.log" 2>/dev/null # Save a copy of the new module mkdir "$base_dir/module" >/dev/null for ((count=0; count < ${#built_module_name[@]}; count++)); do local the_module="$build_dir/${built_module_location[$count]}${built_module_name[$count]}" local built_module="$the_module$module_uncompressed_suffix" local compressed_module="$the_module$module_suffix" [[ ${strip[$count]} != no ]] && strip -g "$built_module" if (( do_signing )); then echo "Signing module $built_module" "$sign_file" "$sign_hash" "$mok_signing_key" "$mok_certificate" "$built_module" fi if [[ $module_compressed_suffix = .gz ]]; then gzip -9f "$built_module" || compressed_module="" elif [[ $module_compressed_suffix = .xz ]]; then xz --check=crc32 --lzma2=dict=1MiB -f "$built_module" || compressed_module="" elif [[ $module_compressed_suffix = .zst ]]; then zstd -q -f -T0 -19 "$built_module" || compressed_module="" fi if [[ -n $compressed_module ]]; then cp -f "$compressed_module" "$base_dir/module/${dest_module_name[$count]}$module_suffix" >/dev/null else cp -f "$built_module" "$base_dir/module/${dest_module_name[$count]}$module_uncompressed_suffix" >/dev/null fi done # Run the post_build script run_build_script post_build "$post_build" } # Clean up after a build. clean_build() { # Run the clean commands cd "$dkms_tree/$module/$module_version/build" invoke_command "$clean" "Cleaning build area" '' background cd - >/dev/null # Clean the build directory rm -rf "$dkms_tree/$module/$module_version/build" } do_build() { set_kernel_source_dir_and_kconfig "$kernelver" prepare_kernel "$kernelver" "$arch" prepare_signing prepare_build actual_build clean_build } # Force the installation of a module if this is listed # in the files in $forced_modules_dir, if any force_installation() { forced_modules_dir="/usr/share/dkms/modules_to_force_install" to_force="" if [[ -d $forced_modules_dir ]]; then for elem in $forced_modules_dir/*; do if [[ -e $elem ]]; then to_force="$to_force $(cat $elem)" fi done for elem in $to_force; do if [[ ${1} = ${elem} ]]; then echo "force" return 0 elif [[ ${1}_version-override = ${elem} ]]; then echo "version-override" return 0 fi done fi return 1 } # Install a previously built module # There are huge swaths of code here that special-case for various distros. # They should be split into their own functions. do_install() { # If the module has not been built, try to build it first. is_module_built "$module" "$module_version" "$kernelver" "$arch" || do_build local base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch" # Save the status of $force tmp_force="$force" # If the module is set to be force-installed local ret=$(force_installation $module) if [[ "$ret" == "force" ]];then force="true" echo "Forcing installation of $module" elif [[ "$ret" == "version-override" ]];then force_version_override="true" echo "Forcing version override of $module" fi # Make sure that kernel exists to install into [[ -e $install_tree/$kernelver ]] || die 6 \ $"The directory $install_tree/$kernelver doesn't exist." \ $"You cannot install a module onto a non-existant kernel." # Read the conf file read_conf_or_die "$kernelver" "$arch" # Check that its not already installed (kernel symlink) is_module_installed "$module" "$module_version" "$kernelver" "$arch" && die 5 \ $"This module/version combo is already installed for kernel $kernelver ($arch)." # If upgrading using rpm_safe_upgrade, go ahead and force the install # else we can wind up with the first half of an upgrade failing to install anything, # while the second half of the upgrade, the removal, then succeeds, leaving us with # nothing installed. [[ $rpm_safe_upgrade ]] && force="true" # Save the original_module if one exists, none have been saved before, and this is the first module for this kernel local lib_tree="$install_tree/$kernelver" local any_module_installed local count for ((count=0; count < ${#built_module_name[@]}; count++)); do echo $"" echo $"${dest_module_name[$count]}$module_suffix:" # Check this version against what is already in the kernel check_version_sanity "$kernelver" "$arch" "$obsolete_by" "${dest_module_name[$count]}" || continue if ((count == 0)) && ! run_build_script pre_install "$pre_install" && ! [[ $force ]]; then die 101 $"pre_install failed, aborting install." \ $"You may override by specifying --force." fi local m=${dest_module_name[$count]} local installed_modules=$(find_module "$lib_tree" "$m") local module_count=${#installed_modules[@]} echo $" - Original module" local original_copy=$(compressed_or_uncompressed "$dkms_tree/$module/original_module/$kernelver/$arch" "$m") if [[ -L $dkms_tree/$module/kernel-$kernelver-$arch && -n "$original_copy" ]]; then echo $" - An original module was already stored during a previous install" elif ! [[ -L $dkms_tree/$module/kernel-$kernelver-$arch ]]; then local archive_pref1=$(compressed_or_uncompressed "$lib_tree/extra" "$m") local archive_pref2=$(compressed_or_uncompressed "$lib_tree/updates" "$m") local archive_pref3=$(compressed_or_uncompressed "$lib_tree${dest_module_location[$count]}" "$m") local archive_pref4="" ((module_count == 1)) && archive_pref4=${installed_modules[0]} local original_module="" local found_orginal="" for original_module in $archive_pref1 $archive_pref2 $archive_pref3 $archive_pref4; do [[ -f $original_module ]] || continue case "$running_distribution" in debian* | ubuntu* ) ;; *) echo $" - Found $original_module" echo $" - Storing in $dkms_tree/$module/original_module/$kernelver/$arch/" echo $" - Archiving for uninstallation purposes" mkdir -p "$dkms_tree/$module/original_module/$kernelver/$arch" mv -f "$original_module" "$dkms_tree/$module/original_module/$kernelver/$arch/" ;; esac found_original="yes" break done if [[ ! $found_original ]] && ((module_count > 1)); then echo $" - Multiple original modules exist but DKMS does not know which to pick" echo $" - Due to the confusion, none will be considered during a later uninstall" elif [[ ! $found_original ]]; then echo $" - No original module exists within this kernel" fi else echo $" - This kernel never originally had a module by this name" fi if ((module_count > 1)); then echo $" - Multiple same named modules!" echo $" - $module_count named $m$module_suffix in $lib_tree/" case "$running_distribution" in debian* | ubuntu* ) ;; *) echo $" - All instances of this module will now be stored for reference purposes ONLY" echo $" - Storing in $dkms_tree/$module/original_module/$kernelver/$arch/collisions/" ;; esac for module_dup in $(find_module "$lib_tree" "$m"); do dup_tree="${module_dup#$lib_tree}"; dup_name="${module_dup##*/}" dup_tree="${dup_tree/${dup_name}}" case "$running_distribution" in debian* | ubuntu* ) ;; *) echo $" - Stored $module_dup" mkdir -p "$dkms_tree/$module/original_module/$kernelver/$arch/collisions/$dup_tree" mv -f $module_dup "$dkms_tree/$module/original_module/$kernelver/$arch/collisions/$dup_tree" ;; esac done fi # Copy module to its location echo $" - Installation" echo $" - Installing to $install_tree/$kernelver${dest_module_location[$count]}/" mkdir -p $install_tree/$kernelver${dest_module_location[$count]} [[ $symlink_modules ]] && symlink="-s" local toinstall=$(compressed_or_uncompressed "$base_dir/module" "$m") cp -f $symlink "$toinstall" "$install_tree/$kernelver${dest_module_location[$count]}/${toinstall##*/}" any_module_installed=1 done if ((${#built_module_name[@]} > 0)) && [[ ! "${any_module_installed}" ]]; then die 6 $"Installation aborted." fi # Create the kernel- symlink to designate this version as active rm -f "$dkms_tree/$module/kernel-$kernelver-$arch" 2>/dev/null ln -s "$module_version/$kernelver/$arch" "$dkms_tree/$module/kernel-$kernelver-$arch" 2>/dev/null # Add to kabi-tracking if [[ -z $NO_WEAK_MODULES ]]; then if [[ ${weak_modules} ]]; then echo $"Adding any weak-modules" list_each_installed_module "$module" "$kernelver" "$arch" | ${weak_modules} ${weak_modules_no_initrd} --add-modules fi fi # Run the post_install script run_build_script post_install "$post_install" invoke_command "do_depmod $kernelver" "depmod" '' background || { do_uninstall "$kernelver" "$arch" die 6 $"Problems with depmod detected. Automatically uninstalling this module." \ $"Install Failed (depmod problems). Module rolled back to built state." exit 6 } if [[ $modprobe_on_install ]]; then # Make the newly installed modules available immediately find /sys/devices -name modalias -print0 | xargs -0 cat | sort -u | xargs modprobe -a -b -q if [[ -f /lib/systemd/system/systemd-modules-load.service ]]; then systemctl restart systemd-modules-load.service fi fi # Restore the status of $force force="$tmp_force" } # List each kernel object that has been installed for a particular module. list_each_installed_module() { # $1 = module # $2 = kernel version # $3 = arch local count local real_dest_module_location local mod for ((count=0; count < ${#built_module_name[@]}; count++)); do real_dest_module_location="$(find_actual_dest_module_location $1 $count $2 $3)" mod=$(compressed_or_uncompressed "$install_tree/$2${real_dest_module_location}" "${dest_module_name[$count]}") echo "$mod" done } # Check if either the module source, or the symlink pointing to it is missing # A module can only be in this broken state, if the user or a faulty program # messed up. The module then is considered volatile, because there is no reliable # way to tell if files in the source tree are still in a valid state. # Therefor any action (except 'add', if only the symlink is missing) # to operate on the module has to be refused. # Manual intervention by the user is required to return to a sane state. is_module_broken() { [[ $1 && $2 ]] || return 1 [[ -d $dkms_tree/$1/$2 ]] || return 2 [[ -L $dkms_tree/$1/$2/source && ! -d $dkms_tree/$1/$2/source ]] && return [[ ! -L $dkms_tree/$1/$2/source && -d $source_tree/$1-$2/ ]] && return } is_module_added() { [[ $1 && $2 ]] || return 1 [[ -d $dkms_tree/$1/$2 ]] || return 2 [[ -L $dkms_tree/$1/$2/source && -d $dkms_tree/$1/$2/source ]] || return 2 } is_module_built() { [[ $1 && $2 && $3 && $4 ]] || return 1 local d="$dkms_tree/$1/$2/$3/$4" m='' [[ -d $d/module ]] || return 1 local default_conf="$dkms_tree/$1/$2/source/dkms.conf" # If a custom dkms.conf was specified use it, otherwise use the default one. local real_conf="${conf:-${default_conf}}" read_conf_or_die "$3" "$4" "$real_conf" set_module_suffix "$3" for m in "${dest_module_name[@]}"; do local t=$(compressed_or_uncompressed "$d/module" "$m") test -n "$t" || return 1 done } # This assumes we have already checked to see if the module has been built. _is_module_installed() { [[ $1 && $2 && $3 && $4 ]] || return 1 local d="$dkms_tree/$1/$2/$3/$4" local k="$dkms_tree/$1/kernel-$3-$4" [[ -L $k && $(readlink -f $k) = $d ]] } # This does not. is_module_installed() { is_module_built "$@" && _is_module_installed "$@"; } maybe_add_module() ( is_module_added "$1" "$2" && { echo $"Module $1/$2 already added." return 0 } module="$1" module_version="$2" add_module ) maybe_build_module() ( is_module_built "$1" "$2" "$3" "$4" && { if [[ "$force" = "true" ]]; then do_unbuild "$3" "$4" else echo $"Module $1/$2 already built for kernel $3 ($4), skip." \ $"You may override by specifying --force." return 0 fi } module="$1" module_version="$2" kernelver="$3" arch="$4" do_build ) maybe_install_module() ( is_module_installed "$1" "$2" "$3" "$4" && { if [[ "$force" = "true" ]]; then do_uninstall "$3" "$4" else echo $"Module $1/$2 already installed on kernel $3 ($4), skip." \ $"You may override by specifying --force." return 0 fi } module="$1" module_version="$2" kernelver="$3" arch="$4" do_install ) build_module() { local i=0 for ((i=0; i < ${#kernelver[@]}; i++)); do maybe_build_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}" done } install_module() { local i=0 for ((i=0; i < ${#kernelver[@]}; i++)); do maybe_install_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}" done } possible_dest_module_locations() { # $1 = count # There are two places an installed module may really be: # 1) "$install_tree/$kernelver/${dest_module_location[$count]}/${dest_module_name[$count]}$module_suffix" # 2) "$install_tree/$kernelver/${DEST_MODULE_LOCATION[$count]}/${dest_module_name[$count]}$module_suffix" # override_dest_module_location() is what controls whether or not they're the same. local location location[0]="${dest_module_location[$count]}" [[ ${DEST_MODULE_LOCATION[$count]} != ${dest_module_location[$count]} ]] && \ location[1]="${DEST_MODULE_LOCATION[$count]}" echo "${location[@]}" } find_actual_dest_module_location() { local module="$1" local count="$2" local kernelver="$3" local arch="$4" local locations="$(possible_dest_module_locations $count)" local l local dkms_owned local installed dkms_owned=$(compressed_or_uncompressed "${dkms_tree}/${module}/kernel-${kernelver}-${arch}/module" "${dest_module_name[$count]}") for l in $locations; do installed=$(compressed_or_uncompressed "${install_tree}/${kernelver}${l}" "${dest_module_name[${count}]}") if [[ -n "${installed}" ]] && compare_module_version "${dkms_owned}" "${installed}" &>/dev/null; then echo "${l}" return 0 fi done } # Remove compiled DKMS modules from any kernels they are installed in. do_uninstall() { # $1 = kernel version # $2 = arch echo $"Module $module-$module_version for kernel $1 ($2)." set_module_suffix "$1" # If kernel- symlink points to this module, check for original_module and put it back local was_active="" local kernel_symlink=$(readlink -f "$dkms_tree/$module/kernel-$1-$2") local real_dest_module_location if [[ $kernel_symlink = $dkms_tree/$module/$module_version/$1/$2 ]]; then was_active="true" echo $"Before uninstall, this module version was ACTIVE on this kernel." # remove kabi-tracking if last instance removed if [[ -z $NO_WEAK_MODULES ]]; then if [[ ${weak_modules} ]] && (module_status_built $module $module_version |grep -q "installed"); then echo $"Removing any linked weak-modules" list_each_installed_module "$module" "$1" "$2" | ${weak_modules} ${weak_modules_no_initrd} --remove-modules fi fi for ((count=0; count < ${#built_module_name[@]}; count++)); do real_dest_module_location="$(find_actual_dest_module_location $module $count $1 $2)" echo $"" echo $"${dest_module_name[$count]}$module_suffix:" echo $" - Uninstallation" if [[ ${real_dest_module_location} ]]; then echo $" - Deleting from: $install_tree/$1${real_dest_module_location}/" rm -f "$install_tree/$1${real_dest_module_location}/${dest_module_name[$count]}$module_uncompressed_suffix"* dir_to_remove="${real_dest_module_location#/}" while [[ ${dir_to_remove} != ${dir_to_remove#/} ]]; do dir_to_remove="${dir_to_remove#/}" done (if cd "$install_tree/$1"; then rpm -qf "${dir_to_remove}" >/dev/null 2>&1 || rmdir -p --ignore-fail-on-non-empty "${dir_to_remove}"; fi || true) else echo $" - Module was not found within $install_tree/$1/" fi echo $" - Original module" local origmod=$(compressed_or_uncompressed "$dkms_tree/$module/original_module/$1/$2" "${dest_module_name[$count]}") if [[ -n $origmod ]]; then case "$running_distribution" in debian* | ubuntu* ) ;; *) echo $" - Archived original module found in the DKMS tree" echo $" - Moving it to: $install_tree/$1${DEST_MODULE_LOCATION[$count]}/" mkdir -p "$install_tree/$1${DEST_MODULE_LOCATION[$count]}/" mv -f "$origmod" "$install_tree/$1${DEST_MODULE_LOCATION[$count]}/" 2>/dev/null ;; esac else echo $" - No original module was found for this module on this kernel." echo $" - Use the dkms install command to reinstall any previous module version." fi done rm -f "$dkms_tree/$module/kernel-$1-$2" else echo $"This module version was INACTIVE for this kernel." fi # Run the post_remove script run_build_script post_remove "$post_remove" # Run depmod because we changed $install_tree invoke_command "do_depmod $1" "depmod" '' background # Delete the original_module if nothing for this kernel is installed anymore if [[ $was_active && -d $dkms_tree/$module/original_module/$1/$2 && ! -d $dkms_tree/$module/original_module/$1/$2/collisions ]]; then echo $"" echo $"Removing original_module from DKMS tree for kernel $1 ($2)" rm -rf "$dkms_tree/$module/original_module/$1/$2" 2>/dev/null [[ $(find $dkms_tree/$module/original_module/$1/* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "$dkms_tree/$module/original_module/$1" elif [[ $was_active && -d $dkms_tree/$module/original_module/$1/$2/collisions ]]; then echo $"" echo $"Keeping directory $dkms_tree/$module/original_module/$1/$2/collisions/" echo $"for your reference purposes. Your kernel originally contained multiple" echo $"same-named modules and this directory is now where these are located." fi [[ $(find $dkms_tree/$module/original_module/* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "$dkms_tree/$module/original_module" } module_is_broken_and_die() { is_module_broken "$module" "$module_version" && die 4 $"$module/$module_version is broken!"\ $"Missing the source directory or the symbolic link pointing to it."\ $"Manual intervention is required!" } module_is_added_or_die() { is_module_added "$module" "$module_version" || die 3 \ $"The module/version combo: $module-$module_version is not located in the DKMS tree." } maybe_unbuild_module() { is_module_built "$module" "$module_version" "$1" "$2" || { echo $"Module $module $module_version is not built for kernel $1 ($2)."\ $"Skipping..." return 0 } do_unbuild "$1" "$2" } maybe_uninstall_module() { is_module_installed "$module" "$module_version" "$1" "$2" || { echo $"Module $module $module_version is not installed for kernel $1 ($2)."\ $"Skipping..." return 0 } do_uninstall "$1" "$2" } uninstall_module() { local i for ((i=0; i < ${#kernelver[@]}; i++)); do maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}" done } do_unbuild() { # Delete or "unbuild" the $kernel_version/$arch_used part of the tree rm -rf "$dkms_tree/$module/$module_version/$1/$2" [[ $(find $dkms_tree/$module/$module_version/$1/* -maxdepth 0 -type d 2>/dev/null) ]] || \ rm -rf "$dkms_tree/$module/$module_version/$1" } # Remove the build module, w/o removing/unregistering it. # This uninstalls any installed modules along the way unbuild_module() { local i for ((i=0; i < ${#kernelver[@]}; i++)); do maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}" maybe_unbuild_module "${kernelver[$i]}" "${arch[$i]}" done } # Unregister a DKMS module. This uninstalls any installed modules along the way. remove_module() { # Do --rpm_safe_upgrade check (exit out and don't do remove if inter-release RPM upgrade scenario occurs) if [[ $rpm_safe_upgrade ]]; then local pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status) local time_stamp=$(ps -o lstart --no-headers -p $pppid 2>/dev/null) for lock_file in $tmp_location/dkms_rpm_safe_upgrade_lock.$pppid.*; do [[ -f $lock_file ]] || continue lock_head=$(head -n 1 $lock_file 2>/dev/null) lock_tail=$(tail -n 1 $lock_file 2>/dev/null) [[ $lock_head = $module-$module_version && $time_stamp && $lock_tail = $time_stamp ]] || continue rm -f $lock_file die 0 $"Remove cancelled because --rpm_safe_upgrade scenario detected." done fi local i for ((i=0; i < ${#kernelver[@]}; i++)); do maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}" maybe_unbuild_module "${kernelver[$i]}" "${arch[$i]}" done # Delete the $module_version part of the tree if no other $module_version/$kernel_version dirs exist if ! find $dkms_tree/$module/$module_version/* -maxdepth 0 -type d 2>/dev/null | grep -Eqv "(build|tarball|driver_disk|rpm|deb|source)$"; then echo $"Deleting module $module-$module_version completely from the DKMS tree." rm -rf "$dkms_tree/$module/$module_version" fi # Get rid of any remnant directories if necessary if (($(ls "$dkms_tree/$module" | wc -w | awk '{print $1}') == 0)); then rm -rf "$dkms_tree/$module" 2>/dev/null fi } # Given a kernel object, figure out which DKMS module it is from. find_module_from_ko() { local ko="$1" local basename_ko="${ko##*/}" local module local kernellink for kernellink in "$dkms_tree"/*/kernel-*; do [[ -L $kernellink ]] || continue module=${kernellink#$dkms_tree/} module=${module%/kernel-*} diff "$kernellink/module/${basename_ko}" "${ko}" >/dev/null 2>&1 || continue rest=$(readlink $kernellink) echo "$module/$rest" return 0 done return 1 } # Check to see if modules meeting the passed parameters are weak-installed. # This function's calling convention is different from the usual DKMS status # checking functions -- the kernel version we usually have is the one we are currently # running on, not necessarily the one we compiled the module for. module_status_weak() { # $1 = module, $2 = module version, $3 = kernel version weak installed to, # $4 = kernel arch, $5 = kernel version built for [[ -z $NO_WEAK_MODULES ]] || return 1 [[ $weak_modules ]] || return 1 local m v k a kern weak_ko mod installed_ko f ret=1 oifs=$IFS local -A already_found for weak_ko in "$install_tree/"*/weak-updates/*; do [[ -e $weak_ko ]] || continue [[ -L $weak_ko ]] && installed_ko="$(readlink -f "$weak_ko")" || continue IFS=/ read m v k a < <(IFS=$oifs find_module_from_ko "$weak_ko") || continue kern=${weak_ko#$install_tree/} kern=${kern%/weak-updates/*} [[ $m = ${1:-*} && $v = ${2:-*} && $k = ${5:-*} && $a = ${4:-*} && $kern = ${3:-*} ]] || continue already_found[$m/$v/$kern/$a/$k]+=${weak_ko##*/}" " done # Check to see that all ko's are present for each module for mod in ${!already_found[@]}; do IFS=/ read m v k a kern <<< "$mod" # ensure each module is weak linked for installed_ko in $(find $dkms_tree/$m/$v/$kern/$a/module -type f); do [[ ${already_found[$mod]} != *"$installed_ko"* ]] && continue 2 done ret=0 echo "installed-weak $mod" done return $ret } # Print the requested status lines for weak-installed modules. do_status_weak() { local mvka m v k a kern status while read status mvka; do IFS=/ read m v k a kern <<< "$mvka" echo "$m, $v, $k, $a: installed-weak from $kern" done < <(module_status_weak "$@") } # Spit out all the extra status information that people running DKMS are # interested in, but that the DKMS internals do not usually care about. module_status_built_extra() ( set_module_suffix "$3" read_conf "$3" "$4" "$dkms_tree/$1/$2/source/dkms.conf" 2>/dev/null [[ -d $dkms_tree/$1/original_module/$3/$4 ]] && echo -n " (original_module exists)" for ((count=0; count < ${#dest_module_name[@]}; count++)); do tree_mod=$(compressed_or_uncompressed "$dkms_tree/$1/$2/$3/$4/module" "${dest_module_name[$count]}") if ! [[ -n "$tree_mod" ]]; then echo -n " (WARNING! Missing some built modules!)" elif _is_module_installed "$@"; then real_dest="$(find_actual_dest_module_location "$1" $count "$3" "$4")" real_dest_mod=$(compressed_or_uncompressed "$install_tree/$3${real_dest}" "${dest_module_name[$count]}") if ! diff -q "$tree_mod" "$real_dest_mod" >/dev/null 2>&1; then echo -n " (WARNING! Diff between built and installed module!)" fi fi done ) # Return a list of all the modules that are either built or installed. # This and module_status do some juggling of $IFS to ensure that # we do not get word splitting where it would be inconvenient. module_status_built() { local ret=1 directory ka k a state oifs="$IFS" IFS='' for directory in "$dkms_tree/$1/$2/"${3:-+([0-9]).*}/${4:-*}; do IFS="$oifs" ka="${directory#$dkms_tree/$1/$2/}" k="${ka%/*}" a="${ka#*/}" is_module_built "$1" "$2" "$k" "$a" || continue ret=0 state="built" _is_module_installed "$1" "$2" "$k" "$a" && state="installed" echo "$state $1/$2/$k/$a" IFS='' done IFS="$oifs" return $ret } # Return the status of all modules that have been added, built, or installed. module_status() { local oifs="$IFS" IFS='' mv m v directory ret=1 for directory in "$dkms_tree/"${1:-*}/${2:-*}; do IFS="$oifs" mv="${directory#$dkms_tree/}" m="${mv%/*}" v="${mv#*/}" is_module_broken "$m" "$v" && { echo "broken $m/$v"; continue; } is_module_added "$m" "$v" || continue ret=0 module_status_built "$m" "$v" "$3" "$4" || echo "added $m/$v" IFS='' done IFS="$oifs" return $ret } # Print out the status in the format that people who call DKMS expect. # Internal callers should use the module_status functions, as their output # is easier to parse. do_status() { local status mvka m v k a # separate deprecation warnings from status output local tmpfile=$(mktemp_or_die) (module_status "$@") >"$tmpfile" while read status mvka; do IFS=/ read m v k a <<< "$mvka" case $status in broken) echo "$m/$v: $status" error $"$m/$v: Missing the module source directory or the symbolic link pointing to it."\ $"Manual intervention is required!" ;; added) echo "$m/$v: $status" ;; built|installed) echo -n "$m/$v, $k, $a: $status" module_status_built_extra "$m" "$v" "$k" "$a" echo ;; esac done < "$tmpfile" rm "$tmpfile" } # Show all our status in the format that external callers expect, even # though it is slightly harder to parse. show_status() { local j state_array if ((${#kernelver[@]} == 0)); then do_status "$module" "$module_version" "$kernelver" "$arch" do_status_weak "$module" "$module_version" "$kernelver" "$arch" else for ((j=0; j < ${#kernelver[@]}; j++)); do do_status "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}" do_status_weak "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}" done fi } make_tarball() { # Read the conf file read_conf_or_die "$kernelver" "$arch" local -r temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX) trap "rm -rf $temp_dir_name" EXIT mkdir -p $temp_dir_name/dkms_main_tree if [[ $source_only ]]; then kernel_version_list="source-only" else local i for ((i=0; i<${#kernelver[@]}; i++)); do local -r intree_module_dir="$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}" local -r temp_module_dir="$temp_dir_name/dkms_main_tree/${kernelver[$i]}" if ! [[ -d "$intree_module_dir" ]]; then die 6 $"No modules built for ${kernelver[$i]} (${arch[$i]})." \ $"Modules must already be in the built state before using mktarball." fi set_module_suffix "${kernelver[$i]}" echo "Marking modules for ${kernelver[$i]} (${arch[$i]}) for archiving..." if [[ ! $kernel_version_list ]]; then kernel_version_list="kernel${kernelver[$i]}-${arch[$i]}" else kernel_version_list="${kernel_version_list}-kernel${kernelver[$i]}-${arch[$i]}" fi mkdir -p "$temp_module_dir" cp -rf "$intree_module_dir" "$temp_module_dir" done fi local -r source_dir="$dkms_tree/$module/$module_version/source" # Copy the source_tree or make special binaries-only structure if [[ $binaries_only ]]; then local -r binary_only_dir="$temp_dir_name/dkms_binaries_only" echo $"" echo $"Creating tarball structure to specifically accomodate binaries." mkdir "$binary_only_dir" echo "$module" > "$binary_only_dir/PACKAGE_NAME" echo "$module_version" > "$binary_only_dir/PACKAGE_VERSION" [[ ! $conf ]] && conf="$source_dir/dkms.conf" cp -f $conf "$binary_only_dir/" 2>/dev/null else echo $"" echo $"Marking $source_dir for archiving..." mkdir -p $temp_dir_name/dkms_source_tree cp -rf $source_dir/* $temp_dir_name/dkms_source_tree fi if (( $(echo $kernel_version_list | wc -m | awk {'print $1'}) > 200 )); then kernel_version_list="manykernels" fi local tarball_name="$module-$module_version-$kernel_version_list.dkms.tar.gz" local tarball_dest="$dkms_tree/$module/$module_version/tarball/" if [[ $archive_location ]]; then tarball_name="${archive_location##*/}" if [[ ${archive_location%/*} != $archive_location ]]; then tarball_dest="${archive_location%/*}" fi fi echo $"" echo $"Tarball location: $tarball_dest/$tarball_name" if [[ ! -d $tarball_dest ]]; then if ! mkdir -p "$tarball_dest" 2>/dev/null; then die 9 $"Missing write permissions for $tarball_dest." fi fi [[ -w $tarball_dest ]] || die 9 $"Missing write permissions for $tarball_dest." if ! tar -C $temp_dir_name -caf $tarball_dest/$tarball_name . 2>/dev/null; then die 6 $"Failed to make tarball." fi } # A tiny helper function to make sure dkms.conf describes a valid package. get_pkginfo_from_conf() { [[ -f $1 && $1 = *dkms.conf ]] || return read_conf_or_die "$kernelver" "$arch" "$1" [[ $PACKAGE_NAME && $PACKAGE_VERSION ]] } # Unpack a DKMS tarball from a few different supported formats. # We expect $archive_location to have been passed either as a raw argument or # with --archive. load_tarball() { # Error out if $archive_location does not exist if [[ ! -e $archive_location ]]; then die 2 $"$archive_location does not exist." fi # If it is an .rpm file. install it with rpm, run an autoinstall, and then exit. if [[ $archive_location = *.rpm ]]; then if rpm -Uvh "$archive_location"; then autoinstall exit $? else die 9 $"Unable to install $archive_location using rpm." \ $"Check to ensure that your system can install .rpm files." fi fi # Untar it into $tmp_location local -r temp_dir_name=$(mktemp_or_die -d $tmp_location/dkms.XXXXXX) trap "rm -rf $temp_dir_name" EXIT tar -xaf $archive_location -C $temp_dir_name if [[ ! -d $temp_dir_name/dkms_main_tree ]]; then # Tarball was not generated from mktarball. # Just find the dkms.conf file and load the source. conf=$(find $temp_dir_name/ -name dkms.conf 2>/dev/null | head -n 1) if [[ ! $conf ]]; then die 3 $"Tarball does not appear to be a correctly formed DKMS archive. No dkms.conf found within it." fi add_source_tree "${conf%dkms.conf}" return fi # Make sure its a sane tarball. Sane ones will have one of the two # directories we test for. for loc in dkms_source_tree dkms_binaries_only ''; do if [[ ! $loc ]]; then die 7 $"No valid dkms.conf in dkms_source_tree or dkms_binaries_only." \ $"$archive_location is not a valid DKMS tarball." fi local conf="$temp_dir_name/$loc/dkms.conf" [[ -f $conf ]] || continue if ! get_pkginfo_from_conf "$conf"; then echo >&2 echo $"Malformed dkms.conf, refusing to load." >&2 continue fi if is_module_added "$PACKAGE_NAME" "$PACKAGE_VERSION" && \ [[ ! $force ]]; then die 8 $"$PACKAGE_NAME-$PACKAGE_VERSION is already added!" \ $"Aborting." fi # Success! break done module="$PACKAGE_NAME"; module_version="$PACKAGE_VERSION" echo $"" echo $"Loading tarball for $module-$module_version" case $loc in dkms_source_tree) add_source_tree "$temp_dir_name/dkms_source_tree" ;; dkms_binaries_only) #if there is a source tree on the system already, don't build a binaries stub if [[ ! -d $source_tree/$module-$module_version ]]; then local -r source_dir="$dkms_tree/$module/$module_version/source" echo $"Creating $source_dir" mkdir -p "$source_dir" echo $"Copying dkms.conf to $source_dir ..." cp -rf "$temp_dir_name/dkms_binaries_only/dkms.conf" "$source_dir" fi ;; esac # At this point, the source has been copied to the appropriate location # and registered with dkms, or a binary-only config has been noted. # Now, add any included precompiled modules. # Load precompiled modules. for directory in "$temp_dir_name/dkms_main_tree"/*/*; do [[ -d $directory ]] || continue local -r kernel_arch_to_load=${directory/*dkms_main_tree\/} local -r dkms_dir_location="$dkms_tree/$module/$module_version/$kernel_arch_to_load" if [[ -d $dkms_dir_location && ! $force ]]; then warn $"$dkms_dir_location already exists. Skipping..." else echo $"Loading $dkms_dir_location..." rm -rf $dkms_dir_location mkdir -p $dkms_dir_location cp -rf $directory/* $dkms_dir_location/ fi done [[ $loc != dkms_binaries_only ]] || [[ -d $source_tree/$module-$module_version ]] } run_match() { set_kernel_source_dir_and_kconfig "$kernelver" # Error if $template_kernel is unset if [[ ! $template_kernel ]]; then die 1 $"Invalid number of parameters passed." \ $"Usage: match --templatekernel= -k " \ $" or: match --templatekernel= -k " fi # Error out if $template_kernel = $kernel_version if [[ $template_kernel = $kernelver ]]; then die 2 $"The templatekernel and the specified kernel version are the same." fi # Read in the status of template_kernel local template_kernel_status=$(do_status '' '' $template_kernel $arch | grep ": installed") # If $module is set, grep the status only for that module if [[ $module ]]; then # Make sure that its installed in the first place if ! [[ -d $dkms_tree/$module/ ]]; then die 3 $"The module: $module is not located in the DKMS tree." fi template_kernel_status=$(echo "$template_kernel_status" | grep "^$module,") fi echo $"" echo $"Matching modules in kernel: $kernelver ($arch)" echo $"to the configuration of kernel: $template_kernel ($arch)" # Prepare the kernel just once but only if there is actual work to do if [[ ! $template_kernel_status ]]; then echo $"" echo $"There is nothing to be done for this match." return 0 fi prepare_kernel "$kernelver" "$arch" # Iterate over the kernel_status and match kernel to the template_kernel while read template_line; do template_module=$(echo "$template_line" | awk {'print $1'} | sed 's/,$//') template_version=$(echo "$template_line" | awk {'print $2'} | sed 's/,$//') # Print out a match header echo $"Module: $template_module" echo $"Version: $template_version" # Continue if the status is broken, as there is nothing we can do if is_module_broken "$template_module" "$template_version"; then error $"$template_module/$template_version is broken!"\ $"Missing the source directory or the symbolic link pointing to it."\ $"Manual intervention is required!" continue fi maybe_build_module "$template_module" "$template_version" "$kernelver" "$arch" maybe_install_module "$template_module" "$template_version" "$kernelver" "$arch" done < <(echo "$template_kernel_status") } report_build_problem() { # If apport is on the system, files a build problem if [[ -x /usr/share/apport/apport ]] && which python3 >/dev/null; then python3 /usr/share/apport/package-hooks/dkms_packages.py -m $module -v $module_version -k ${kernelver[0]} fi die "$@" } # Little helper function for reading args from the commandline. # it automatically handles -a b and -a=b variants, and returns 1 if # we need to shift $3. read_arg() { # $1 = arg name # $2 = arg value # $3 = arg parameter local rematch='^[^=]*=(.*)$' if [[ $2 =~ $rematch ]]; then read "$1" <<< "${BASH_REMATCH[1]}" else read "$1" <<< "$3" # There is no way to shift our callers args, so # return 1 to indicate they should do it instead. return 1 fi } # A couple of helper functions for parsing out our most common arguments. # This one allows you to pass -k kernel.version-extra/arch instead of # -k kernel-version.extra -a arch. # This makes it harder to pass mismatching numbers of kernel/arch pairs, because # they are all passed at the same time. parse_kernelarch(){ if [[ $1 =~ $mv_re ]]; then kernelver[${#kernelver[@]}]="${BASH_REMATCH[1]}" arch[${#arch[@]}]="${BASH_REMATCH[2]}" else kernelver[${#kernelver[@]}]="$1" fi } # This allows you to pass module and module_version information on the commandline # in a more convenient form. Instead of the mostly mandatory and annoying # -m module -v module_version, you can use either -m module/module_version, # or just a raw module/module_version with no -m parameter. # This vastly improves readability and discoverability of # commands on the commandline. parse_moduleversion(){ if [[ $1 =~ $mv_re ]]; then module="${BASH_REMATCH[1]}" module_version="${BASH_REMATCH[2]}" else module="$1" fi } check_root() { [[ $(id -u) = 0 ]] && return die 1 $"You must be root to use this command." } check_rw_dkms_tree() { [[ -w "$dkms_tree" ]] && return die 1 $"No write access to DKMS tree at ${dkms_tree}" } # Add a passed source tree to the default source location. # We will check the dkms.conf file to make sure it is valid # beforehand. add_source_tree() { local from=$(readlink -f $1) if ! [[ $from && -f $from/dkms.conf ]]; then die 9 $"$1 must contain a dkms.conf file!" fi check_root setup_kernels_arches if ! get_pkginfo_from_conf "$from/dkms.conf" ; then die 10 $"Malformed dkms.conf file. Cannot load source tree." fi module="$PACKAGE_NAME" module_version="$PACKAGE_VERSION" if [[ $force && -d $source_tree/$module-$module_version ]]; then echo >&2 echo $"Forcing install of $module-$module_version" rm -rf "$source_tree/$module-$module_version" fi # We are already installed, just return. case $from in "$source_tree/$module-$module_version") return ;; "$dkms_tree/$module/$version/source") return ;; "$dkms_tree/$module/$version/build") return ;; esac mkdir -p "$source_tree/$module-$module_version" cp -fr "$from"/* "$source_tree/$module-$module_version" } # This code used to be in dkms_autoinstaller. # Moving it into the main dkms script gets rid of a fair amount of duplicate # functionality, and makes it much easier to reinstall DKMS kernel modules # by hand if dkms_autoinstaller is not used. autoinstall() { local status mv mvka m v k a local progress next_depends local -a to_install=() local -a next_install=() local -a known_modules=() local -a installed_modules=() local -a skipped_modules=() local -a failed_modules=() local -A build_depends=() local -A latest=() # Walk through our list of installed and built modules, and create # a list of modules and their latest version. while read status mvka; do IFS='/' read m v k a <<< "$mvka" # If the module status is broken there is nothing that can be done if [[ $status = broken ]]; then error $"$m/$v is broken! Missing the source directory or the symbolic link pointing to it."\ $"Manual intervention is required!" continue fi if [[ -z ${latest[$m]} ]]; then known_modules[${#known_modules[@]}]="$m" latest[$m]="$v" elif [[ ("$(VER "$v")" > "$(VER "${latest["$m"]}")") ]]; then latest["$m"]="$v" fi done < <(module_status) # Walk through our list of known modules, and create # a list of modules that need to be reinstalled. for m in "${known_modules[@]}"; do v="${latest["$m"]}" # If the module is already installed or weak-installed, skip it. if _is_module_installed "$m" "$v" "$kernelver" "$arch"; then installed_modules[${#installed_modules[@]}]="$m" continue fi if module_status_weak "$m" "$v" "$kernelver" "$arch" >/dev/null; then installed_modules[${#installed_modules[@]}]="$m" continue fi # If the module does not want to be autoinstalled, skip it. read_conf_or_die "$kernelver" "$arch" "$dkms_tree/$m/$v/source/dkms.conf" if [[ ! $AUTOINSTALL ]]; then continue fi # Otherwise, autoinstall the latest version we have hanging around. to_install[${#to_install[@]}]="$m/$v" build_depends["$m"]="${BUILD_DEPENDS[@]}" done [[ $to_install ]] || return 0 while true; do progress=0 next_install=( ) # Step 1: Remove installed modules from all dependency lists. for m in ${!build_depends[@]}; do next_depends= for d in ${build_depends[$m]}; do for i in ${installed_modules[@]} ${skipped_modules[@]}; do [[ "$d" = "$i" ]] && continue 2 done next_depends+="$d " done build_depends[$m]="${next_depends%% }" done # Step 2: Install modules that have an empty dependency list. for mv in "${to_install[@]}"; do IFS=/ read m v <<< "$mv" if [[ -z ${build_depends[$m]} ]]; then (module="$m" module_version="$v" kernelver="$kernelver" arch="$arch" install_module) status=$? if (( status == 0 )); then installed_modules[${#installed_modules[@]}]="$m" progress=$(($progress +1)) elif (( status == 77 )); then skipped_modules[${#skipped_modules[@]}]="$m" progress=$(($progress +1)) else failed_modules[${#failed_modules[@]}]="$m($status)" fi else next_install[${#next_install[@]}]="$mv" fi done wait # Step 3: Remove modules that install was attempted for # during Step 2 from the job queue. to_install=( "${next_install[@]}" ) # Step 4: Keep going if at least one module was installed during # this iteration. (( progress > 0 )) || break; done if (( ${#installed_modules[@]} > 0 )); then echo "dkms autoinstall on $kernelver/$arch succeeded for ${installed_modules[@]}" fi if (( ${#skipped_modules[@]} > 0 )); then echo "dkms autoinstall on $kernelver/$arch was skipped for ${skipped_modules[@]}" fi if (( ${#failed_modules[@]} > 0 )); then echo "dkms autoinstall on $kernelver/$arch failed for ${failed_modules[@]}" fi for mv in "${to_install[@]}"; do IFS=/ read m v <<< "$mv" echo "$m/$v autoinstall failed due to missing dependencies: ${build_depends[$m]}" done if (( ${#failed_modules[@]} > 0 || ${#to_install[@]} > 0 )); then die 11 $"One or more modules failed to install during autoinstall." \ $"Refer to previous errors for more information." fi } ############################# #### #### #### Program Starts Here #### #### #### ############################# # Ensure files and directories we create are readable to anyone, # since we aim to build as a non-root user umask 022 # Unset environment variables that may interfere with the build unset CC CXX CFLAGS CXXFLAGS LDFLAGS # Set important variables current_kernel=$(uname -r) current_os=$(uname -s) running_distribution=$(distro_version) || exit dkms_tree="/var/lib/dkms" source_tree="/usr/src" install_tree="@MODDIR@" tmp_location=${TMPDIR:-/tmp} verbose="" symlink_modules="" # Check that we can write temporary files tmpfile=$(mktemp_or_die) echo "Hello, DKMS!" > "$tmpfile" if [[ "$(cat "$tmpfile")" != "Hello, DKMS!" ]]; then warn $"dkms will not function properly without some free space in \$TMPDIR ($tmp_location)." fi rm -f "$tmpfile" # These can come from the environment or the config file [[ ! ${ADDON_MODULES_DIR} && -e /etc/sysconfig/module-init-tools ]] && . /etc/sysconfig/module-init-tools addon_modules_dir="${ADDON_MODULES_DIR}" weak_modules="${WEAK_MODULES_BIN}" # Source in configuration not related to signing read_framework_conf $dkms_framework_nonsigning_variables # Clear out command line argument variables module="" module_version="" template_kernel="" conf="" kernel_config="" kconfig_fromcli="" archive_location="" kernel_source_dir="" ksourcedir_fromcli="" action="" force="" force_version_override="" binaries_only="" source_only="" all="" module_suffix="" module_uncompressed_suffix="" module_compressed_suffix="" rpm_safe_upgrade="" declare -a directive_array=() kernelver=() arch=() weak_modules='' last_mvka='' last_mvka_conf='' try_source_tree='' die_is_fatal="yes" [ -x /sbin/weak-modules ] && weak_modules='/sbin/weak-modules' [ -x /usr/lib/module-init-tools/weak-modules ] && weak_modules='/usr/lib/module-init-tools/weak-modules' no_depmod="" action_re='^(remove|(auto|un)?install|match|mktarball|(un)?build|add|status|ldtarball)$' # Parse command line arguments while (($# > 0)); do case $1 in --module*|-m) read_arg _mv "$1" "$2" || shift parse_moduleversion "$_mv" ;; -v) read_arg module_version "$1" "$2" || shift ;; --kernelver*|-k) read_arg _ka "$1" "$2" || shift parse_kernelarch "$_ka" ;; --templatekernel*) read_arg template_kernel "$1" "$2" || shift ;; -c) read_arg conf "$1" "$2" || shift ;; --quiet|-q) exec >/dev/null 2>&1 ;; --version|-V) echo $"@RELEASE_STRING@" exit 0 ;; --no-initrd) # This is an old option, consume and warn deprecated $"--no-initrd" ;; --no-clean-kernel) # This is an old option, consume and warn deprecated $"--no-clean-kernel" ;; --no-prepare-kernel) # This is an old option, consume and warn deprecated $"--no-prepare-kernel" ;; --binaries-only) binaries_only="binaries-only" ;; --source-only) source_only="source-only" ;; --force) force="true" ;; --force-version-override) force_version_override="true" ;; --all) all="true" ;; --verbose) verbose="true" ;; --rpm_safe_upgrade) rpm_safe_upgrade="true" ;; --dkmstree*) read_arg dkms_tree "$1" "$2" || shift ;; --sourcetree*) read_arg source_tree "$1" "$2" || shift ;; --installtree*) read_arg install_tree "$1" "$2" || shift ;; --symlink-modules) symlink_module="true" ;; --config*) read_arg kernel_config "$1" "$2" || shift kconfig_fromcli="true" ;; --archive*) read_arg archive_location "$1" "$2" || shift ;; --arch*|-a) read_arg _aa "$1" "$2" || shift arch[${#arch[@]}]="$_aa" ;; --kernelsourcedir*) read_arg kernel_source_dir "$1" "$2" || shift ksourcedir_fromcli="true" ;; --directive*) read_arg _da "$1" "$2" || shift directive_array[${#directive_array[@]}]="$_da" ;; --no-depmod) no_depmod="true" ;; --modprobe-on-install) modprobe_on_install="true" ;; --debug) export PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): ' set -x ;; -j) read_arg parallel_jobs "$1" "$2" || shift ;; -*) error $" Unknown option: $1" show_usage exit 2 ;; *) if [[ $1 =~ $action_re ]]; then [[ $action ]] && die 4 $"Cannot specify more than one action." action="$1" # Add actions to the action list elif [[ -f $1 && $1 = *dkms.conf ]]; then try_source_tree="${1%dkms.conf}./" # Flag as a source tree elif [[ -d $1 && -f $1/dkms.conf ]]; then try_source_tree="$1" # ditto elif [[ -f $1 ]]; then archive_location="$1" # It is a file, assume it is an archive. elif [[ ! $module ]]; then parse_moduleversion "$1" # Assume it is a module/version pair. else warn $"I do not know how to handle $1." fi ;; esac shift done # Sanity checking # The <(cmd) idiom does not work if /proc is not mounted read line < <(echo "Hello, DKMS!") if [[ $line != "Hello, DKMS!" ]]; then warn $"dkms will not function properly if /proc is not mounted." fi # Error out if binaries-only is set and source-only is set if [[ $binaries_only && $source_only ]]; then die 8 $" You have specified both --binaries-only and --source-only." \ $"You cannot do this." fi # Error if # of arches doesn't match # of kernels if (( ${#kernelver[@]} != ${#arch[@]} && \ ${#arch[@]} > 1 )); then die 1 $" If more than one arch is specified on the command line, then there" \ $"must be an equal number of kernel versions also specified (1:1 relationship)." fi # Check that kernel version and all aren't both set simultaneously if [[ $kernelver && $all ]]; then die 2 $" You cannot specify a kernel version and also specify" \ $"--all on the command line." fi # Check that arch and all aren't both set simultaneously if [[ $arch && $all ]]; then die 3 $" You cannot specify an arch and also specify" \ $"--all on the command line." fi # Since initramfs/initrd rebuild is not requested, skip it with Redhat's weak-modules if [[ $weak_modules ]]; then weak_modules_no_initrd="--no-initramfs" fi # Default to -j parallel_jobs=${parallel_jobs:-$(get_num_cpus)} # Make sure we're not passing -j0 to make; treat -j0 as just "-j" [[ "$parallel_jobs" = 0 ]] && parallel_jobs="" setup_kernels_arches "$action" case "$action" in remove | unbuild | uninstall) check_module_args $action module_is_broken_and_die module_is_added_or_die [[ $action = uninstall ]] && check_root || check_rw_dkms_tree ${action}_module ;; add | build | install) check_all_is_banned $action # TODO: fix/enable --all [[ $action != add ]] && module_is_broken_and_die [[ $action = install ]] && check_root || check_rw_dkms_tree ${action}_module ;; autoinstall) check_root && autoinstall ;; match) check_root && have_one_kernel "match" && run_match ;; mktarball) check_module_args mktarball module_is_broken_and_die module_is_added_or_die make_tarball ;; status) show_status ;; ldtarball) # Make sure they're root if we're using --force if [[ $(id -u) != 0 ]] && [[ $force = true ]]; then die 1 $"You must be root to use this command with the --force option." fi load_tarball && add_module ;; *) error $"Unknown action specified: \"$action\"" show_usage ;; esac