| ##/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 |
| } |