| ##/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0+ | |
| # Copyright (c) 2017 Oracle. All Rights Reserved. | |
| # | |
| # Routines for messing around with loadable kernel modules | |
| # Return the module name for this fs. | |
| _module_for_fs() | |
| { | |
| echo "${FSTYP}" | |
| } | |
| # Reload a particular module. This module MUST NOT be the module that | |
| # underlies the filesystem. | |
| _reload_module() | |
| { | |
| local module="$1" | |
| _patient_rmmod "${module}" || _fail "${module} unload failed" | |
| modprobe "${module}" || _fail "${module} load failed" | |
| } | |
| # Reload the filesystem module. | |
| _reload_fs_module() | |
| { | |
| local module="$1" | |
| # Unload test fs, try to reload module, remount | |
| local had_testfs="" | |
| local had_scratchfs="" | |
| _check_mounted_on TEST_DEV $TEST_DEV TEST_DIR $TEST_DIR && had_testfs="true" | |
| _check_mounted_on SCRATCH_DEV $SCRATCH_DEV SCRATCH_MNT $SCRATCH_MNT && had_scratchfs="true" | |
| test -n "${had_testfs}" && _test_unmount | |
| test -n "${had_scratchfs}" && _scratch_unmount | |
| _reload_module "${module}" | |
| test -n "${had_scratchfs}" && _scratch_mount 2> /dev/null | |
| test -n "${had_testfs}" && _test_mount 2> /dev/null | |
| } | |
| # Check that we have a module that can be loaded. This module MUST NOT | |
| # be the module that underlies the filesystem. | |
| _require_loadable_module() | |
| { | |
| local module="$1" | |
| modinfo "${module}" > /dev/null 2>&1 || _notrun "${module}: must be a module." | |
| _patient_rmmod "${module}" || _notrun "Require ${module} to be unloadable" | |
| modprobe "${module}" || _notrun "${module} load failed" | |
| } | |
| # Check that the module for FSTYP can be loaded. | |
| _require_loadable_fs_module() | |
| { | |
| local module="$1" | |
| modinfo "${module}" > /dev/null 2>&1 || _notrun "${module}: must be a module." | |
| # Unload test fs, try to reload module, remount | |
| local had_testfs="" | |
| local had_scratchfs="" | |
| _check_mounted_on TEST_DEV $TEST_DEV TEST_DIR $TEST_DIR && had_testfs="true" | |
| _check_mounted_on SCRATCH_DEV $SCRATCH_DEV SCRATCH_MNT $SCRATCH_MNT && had_scratchfs="true" | |
| test -n "${had_testfs}" && _test_unmount | |
| test -n "${had_scratchfs}" && _scratch_unmount | |
| local unload_ok="" | |
| local load_ok="" | |
| _patient_rmmod "${module}" || unload_ok=0 | |
| modprobe "${module}" || load_ok=0 | |
| test -n "${had_scratchfs}" && _scratch_mount 2> /dev/null | |
| test -n "${had_testfs}" && _test_mount 2> /dev/null | |
| test -z "${unload_ok}" || _notrun "Require module ${module} to be unloadable" | |
| test -z "${load_ok}" || _notrun "${module} load failed" | |
| } | |
| # Print the value of a filesystem module parameter | |
| # at /sys/module/$FSTYP/parameters/$PARAM | |
| # | |
| # Usage example (FSTYP=overlay): | |
| # _get_fs_module_param index | |
| _get_fs_module_param() | |
| { | |
| cat /sys/module/${FSTYP}/parameters/${1} 2>/dev/null | |
| } | |
| # checks the refcount and returns 0 if we can safely remove the module. rmmod | |
| # does this check for us, but we can use this to also iterate checking for this | |
| # refcount before we even try to remove the module. This is useful when using | |
| # debug test modules which take a while to quiesce. | |
| _patient_rmmod_check_refcnt() | |
| { | |
| local module=$1 | |
| local refcnt=0 | |
| if [[ -f /sys/module/$module/refcnt ]]; then | |
| refcnt=$(cat /sys/module/$module/refcnt 2>/dev/null) | |
| if [[ $? -ne 0 || $refcnt -eq 0 ]]; then | |
| return 0 | |
| fi | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Patiently tries to wait to remove a module by ensuring first | |
| # the refcnt is 0 and then trying to persistently remove the module within | |
| # the time allowed. The timeout is configurable per test, just set | |
| # MODPROBE_PATIENT_RM_TIMEOUT_SECONDS prior to including this file. | |
| # If you want this to try forever just set MODPROBE_PATIENT_RM_TIMEOUT_SECONDS | |
| # to the special value of "forever". This applies to both cases where kmod | |
| # supports the patient module remover (modrobe -p) and where it does not. | |
| # | |
| # If your version of kmod supports modprobe -p, we instead use that | |
| # instead. Otherwise we have to implement a patient module remover | |
| # ourselves. | |
| _patient_rmmod() | |
| { | |
| local module=$1 | |
| local max_tries_max=$MODPROBE_PATIENT_RM_TIMEOUT_SECONDS | |
| local max_tries=0 | |
| local mod_ret=0 | |
| local refcnt_is_zero=0 | |
| if [[ ! -z $MODPROBE_REMOVE_PATIENT ]]; then | |
| $MODPROBE_REMOVE_PATIENT $module | |
| mod_ret=$? | |
| if [[ $mod_ret -ne 0 ]]; then | |
| echo "kmod patient module removal for $module timed out waiting for refcnt to become 0 using timeout of $max_tries_max returned $mod_ret" | |
| fi | |
| return $mod_ret | |
| fi | |
| max_tries=$max_tries_max | |
| # We must use a string check as otherwise if max_tries is set to | |
| # "forever" and we don't use a string check we can end up skipping | |
| # entering this loop. | |
| while [[ "$max_tries" != "0" ]]; do | |
| _patient_rmmod_check_refcnt $module | |
| if [[ $? -eq 0 ]]; then | |
| refcnt_is_zero=1 | |
| break | |
| fi | |
| sleep 1 | |
| if [[ "$max_tries" == "forever" ]]; then | |
| continue | |
| fi | |
| let max_tries=$max_tries-1 | |
| done | |
| if [[ $refcnt_is_zero -ne 1 ]]; then | |
| echo "custom patient module removal for $module timed out waiting for refcnt to become 0 using timeout of $max_tries_max" | |
| return -1 | |
| fi | |
| # If we ran out of time but our refcnt check confirms we had | |
| # a refcnt of 0, just try to remove the module once. | |
| if [[ "$max_tries" == "0" ]]; then | |
| modprobe -r $module | |
| return $? | |
| fi | |
| # If we have extra time left. Use the time left to now try to | |
| # persistently remove the module. We do this because although through | |
| # the above we found refcnt to be 0, removal can still fail since | |
| # userspace can always race to bump the refcnt. An example is any | |
| # blkdev_open() calls against a block device. These issues have been | |
| # tracked and documented in the following bug reports, which justifies | |
| # our need to do this in userspace: | |
| # https://bugzilla.kernel.org/show_bug.cgi?id=212337 | |
| # https://bugzilla.kernel.org/show_bug.cgi?id=214015 | |
| while [[ $max_tries != 0 ]]; do | |
| if [[ -d /sys/module/$module ]]; then | |
| modprobe -r $module 2> /dev/null | |
| mod_ret=$? | |
| if [[ $mod_ret == 0 ]]; then | |
| break; | |
| fi | |
| sleep 1 | |
| if [[ "$max_tries" == "forever" ]]; then | |
| continue | |
| fi | |
| let max_tries=$max_tries-1 | |
| else | |
| break | |
| fi | |
| done | |
| if [[ $mod_ret -ne 0 ]]; then | |
| echo "custom patient module removal for $module timed out trying to remove $module using timeout of $max_tries_max last try returned $mod_ret" | |
| fi | |
| return $mod_ret | |
| } |