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