common/module: use patient module removal
[xfstests-dev.git] / common / module
1 ##/bin/bash
2 # SPDX-License-Identifier: GPL-2.0+
3 # Copyright (c) 2017 Oracle.  All Rights Reserved.
4 #
5 # Routines for messing around with loadable kernel modules
6
7 # Return the module name for this fs.
8 _module_for_fs()
9 {
10         echo "${FSTYP}"
11 }
12
13 # Reload a particular module.  This module MUST NOT be the module that
14 # underlies the filesystem.
15 _reload_module()
16 {
17         local module="$1"
18
19         _patient_rmmod "${module}" || _fail "${module} unload failed"
20         modprobe "${module}" || _fail "${module} load failed"
21 }
22
23 # Reload the filesystem module.
24 _reload_fs_module()
25 {
26         local module="$1"
27
28         # Unload test fs, try to reload module, remount
29         local had_testfs=""
30         local had_scratchfs=""
31         _check_mounted_on TEST_DEV $TEST_DEV TEST_DIR $TEST_DIR && had_testfs="true"
32         _check_mounted_on SCRATCH_DEV $SCRATCH_DEV SCRATCH_MNT $SCRATCH_MNT && had_scratchfs="true"
33         test -n "${had_testfs}" && _test_unmount
34         test -n "${had_scratchfs}" && _scratch_unmount
35         _reload_module "${module}"
36         test -n "${had_scratchfs}" && _scratch_mount 2> /dev/null
37         test -n "${had_testfs}" && _test_mount 2> /dev/null
38 }
39
40 # Check that we have a module that can be loaded.  This module MUST NOT
41 # be the module that underlies the filesystem.
42 _require_loadable_module()
43 {
44         local module="$1"
45
46         modinfo "${module}" > /dev/null 2>&1 || _notrun "${module}: must be a module."
47         _patient_rmmod "${module}" || _notrun "Require ${module} to be unloadable"
48         modprobe "${module}" || _notrun "${module} load failed"
49 }
50
51 # Check that the module for FSTYP can be loaded.
52 _require_loadable_fs_module()
53 {
54         local module="$1"
55
56         modinfo "${module}" > /dev/null 2>&1 || _notrun "${module}: must be a module."
57
58         # Unload test fs, try to reload module, remount
59         local had_testfs=""
60         local had_scratchfs=""
61         _check_mounted_on TEST_DEV $TEST_DEV TEST_DIR $TEST_DIR && had_testfs="true"
62         _check_mounted_on SCRATCH_DEV $SCRATCH_DEV SCRATCH_MNT $SCRATCH_MNT && had_scratchfs="true"
63         test -n "${had_testfs}" && _test_unmount
64         test -n "${had_scratchfs}" && _scratch_unmount
65         local unload_ok=""
66         local load_ok=""
67         _patient_rmmod "${module}" || unload_ok=0
68         modprobe "${module}" || load_ok=0
69         test -n "${had_scratchfs}" && _scratch_mount 2> /dev/null
70         test -n "${had_testfs}" && _test_mount 2> /dev/null
71         test -z "${unload_ok}" || _notrun "Require module ${module} to be unloadable"
72         test -z "${load_ok}" || _notrun "${module} load failed"
73 }
74
75 # Print the value of a filesystem module parameter
76 # at /sys/module/$FSTYP/parameters/$PARAM
77 #
78 # Usage example (FSTYP=overlay):
79 #   _get_fs_module_param index
80 _get_fs_module_param()
81 {
82         cat /sys/module/${FSTYP}/parameters/${1} 2>/dev/null
83 }
84
85 # checks the refcount and returns 0 if we can safely remove the module. rmmod
86 # does this check for us, but we can use this to also iterate checking for this
87 # refcount before we even try to remove the module. This is useful when using
88 # debug test modules which take a while to quiesce.
89 _patient_rmmod_check_refcnt()
90 {
91         local module=$1
92         local refcnt=0
93
94         if [[ -f /sys/module/$module/refcnt ]]; then
95                 refcnt=$(cat /sys/module/$module/refcnt 2>/dev/null)
96                 if [[ $? -ne 0 || $refcnt -eq 0 ]]; then
97                         return 0
98                 fi
99                 return 1
100         fi
101         return 0
102 }
103
104 # Patiently tries to wait to remove a module by ensuring first
105 # the refcnt is 0 and then trying to persistently remove the module within
106 # the time allowed. The timeout is configurable per test, just set
107 # MODPROBE_PATIENT_RM_TIMEOUT_SECONDS prior to including this file.
108 # If you want this to try forever just set MODPROBE_PATIENT_RM_TIMEOUT_SECONDS
109 # to the special value of "forever". This applies to both cases where kmod
110 # supports the patient module remover (modrobe -p) and where it does not.
111 #
112 # If your version of kmod supports modprobe -p, we instead use that
113 # instead. Otherwise we have to implement a patient module remover
114 # ourselves.
115 _patient_rmmod()
116 {
117         local module=$1
118         local max_tries_max=$MODPROBE_PATIENT_RM_TIMEOUT_SECONDS
119         local max_tries=0
120         local mod_ret=0
121         local refcnt_is_zero=0
122
123         if [[ ! -z $MODPROBE_REMOVE_PATIENT ]]; then
124                 $MODPROBE_REMOVE_PATIENT $module
125                 mod_ret=$?
126                 if [[ $mod_ret -ne 0 ]]; then
127                         echo "kmod patient module removal for $module timed out waiting for refcnt to become 0 using timeout of $max_tries_max returned $mod_ret"
128                 fi
129                 return $mod_ret
130         fi
131
132         max_tries=$max_tries_max
133
134         # We must use a string check as otherwise if max_tries is set to
135         # "forever" and we don't use a string check we can end up skipping
136         # entering this loop.
137         while [[ "$max_tries" != "0" ]]; do
138                 _patient_rmmod_check_refcnt $module
139                 if [[ $? -eq 0 ]]; then
140                         refcnt_is_zero=1
141                         break
142                 fi
143                 sleep 1
144                 if [[ "$max_tries" == "forever" ]]; then
145                         continue
146                 fi
147                 let max_tries=$max_tries-1
148         done
149
150         if [[ $refcnt_is_zero -ne 1 ]]; then
151                 echo "custom patient module removal for $module timed out waiting for refcnt to become 0 using timeout of $max_tries_max"
152                 return -1
153         fi
154
155         # If we ran out of time but our refcnt check confirms we had
156         # a refcnt of 0, just try to remove the module once.
157         if [[ "$max_tries" == "0" ]]; then
158                 modprobe -r $module
159                 return $?
160         fi
161
162         # If we have extra time left. Use the time left to now try to
163         # persistently remove the module. We do this because although through
164         # the above we found refcnt to be 0, removal can still fail since
165         # userspace can always race to bump the refcnt. An example is any
166         # blkdev_open() calls against a block device. These issues have been
167         # tracked and documented in the following bug reports, which justifies
168         # our need to do this in userspace:
169         # https://bugzilla.kernel.org/show_bug.cgi?id=212337
170         # https://bugzilla.kernel.org/show_bug.cgi?id=214015
171         while [[ $max_tries != 0 ]]; do
172                 if [[ -d /sys/module/$module ]]; then
173                         modprobe -r $module 2> /dev/null
174                         mod_ret=$?
175                         if [[ $mod_ret == 0 ]]; then
176                                 break;
177                         fi
178                         sleep 1
179                         if [[ "$max_tries" == "forever" ]]; then
180                                 continue
181                         fi
182                         let max_tries=$max_tries-1
183                 fi
184         done
185
186         if [[ $mod_ret -ne 0 ]]; then
187                 echo "custom patient module removal for $module timed out trying to remove $module using timeout of $max_tries_max last try returned $mod_ret"
188         fi
189
190         return $mod_ret
191 }