summaryrefslogtreecommitdiff
path: root/common/module
blob: 6efab71d348e8ad9c49313c04e74a31f3d49ac24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
##/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
}