xref: /linux/tools/testing/selftests/ublk/test_common.sh (revision aec2f682d47c54ef434b2d440992626d80b1ebdc)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# Derive TID from script name: test_<type>_<num>.sh -> <type>_<num>
5# Can be overridden in test script after sourcing this file
6TID=$(basename "$0" .sh)
7TID=${TID#test_}
8
9UBLK_SKIP_CODE=4
10
11_have_program() {
12	if command -v "$1" >/dev/null 2>&1; then
13		return 0
14	fi
15	return 1
16}
17
18# Sleep with awareness of parallel execution.
19# Usage: _ublk_sleep <normal_secs> <parallel_secs>
20_ublk_sleep() {
21	if [ "${JOBS:-1}" -gt 1 ]; then
22		sleep "$2"
23	else
24		sleep "$1"
25	fi
26}
27
28_get_disk_dev_t() {
29	local dev_id=$1
30	local dev
31	local major
32	local minor
33
34	dev=/dev/ublkb"${dev_id}"
35	major="0x"$(stat -c '%t' "$dev")
36	minor="0x"$(stat -c '%T' "$dev")
37
38	echo $(( (major & 0xfff) << 20 | (minor & 0xfffff) ))
39}
40
41_get_disk_size()
42{
43	lsblk -b -o SIZE -n "$1"
44}
45
46_run_fio_verify_io() {
47	fio --name=verify --rw=randwrite --direct=1 --ioengine=libaio \
48		--bs=8k --iodepth=32 --verify=crc32c --do_verify=1 \
49		--verify_state_save=0 "$@" > /dev/null
50}
51
52_create_backfile() {
53	local index=$1
54	local new_size=$2
55	local old_file
56	local new_file
57
58	old_file="${UBLK_BACKFILES[$index]}"
59	[ -f "$old_file" ] && rm -f "$old_file"
60
61	new_file=$(mktemp ${UBLK_TEST_DIR}/ublk_file_"${new_size}"_XXXXX)
62	truncate -s "${new_size}" "${new_file}"
63	UBLK_BACKFILES["$index"]="$new_file"
64}
65
66_remove_files() {
67	local file
68
69	for file in "${UBLK_BACKFILES[@]}"; do
70		[ -f "$file" ] && rm -f "$file"
71	done
72	[ -f "$UBLK_TMP" ] && rm -f "$UBLK_TMP"
73}
74
75_create_tmp_dir() {
76	local my_file;
77
78	my_file=$(mktemp -d ${UBLK_TEST_DIR}/ublk_dir_XXXXX)
79	echo "$my_file"
80}
81
82_remove_tmp_dir() {
83	local dir=$1
84
85	[ -d "$dir" ] && rmdir "$dir"
86}
87
88_mkfs_mount_test()
89{
90	local dev=$1
91	shift
92	local err_code=0
93	local mnt_dir;
94
95	mnt_dir=$(_create_tmp_dir)
96	mkfs.ext4 -F "$dev" > /dev/null 2>&1
97	err_code=$?
98	if [ $err_code -ne 0 ]; then
99		return $err_code
100	fi
101
102	mount -t ext4 "$dev" "$mnt_dir" > /dev/null 2>&1
103	if [ $# -gt 0 ]; then
104		cd "$mnt_dir" && "$@"
105		err_code=$?
106		cd - > /dev/null
107	fi
108	umount "$dev"
109	if [ $err_code -eq 0 ]; then
110		err_code=$?
111	fi
112	_remove_tmp_dir "$mnt_dir"
113	return $err_code
114}
115
116_check_root() {
117	local ksft_skip=4
118
119	if [ $UID != 0 ]; then
120		echo please run this as root >&2
121		exit $ksft_skip
122	fi
123}
124
125_get_ublk_dev_state() {
126	${UBLK_PROG} list -n "$1" | grep "state" | awk '{print $11}'
127}
128
129_get_ublk_daemon_pid() {
130	${UBLK_PROG} list -n "$1" | grep "pid" | awk '{print $7}'
131}
132
133_prep_test() {
134	_check_root
135	local type=$1
136	shift 1
137	modprobe ublk_drv > /dev/null 2>&1
138	local base_dir=${TMPDIR:-./ublktest-dir}
139	mkdir -p "$base_dir"
140	UBLK_TEST_DIR=$(mktemp -d ${base_dir}/${TID}.XXXXXX)
141	UBLK_TEST_DIR=$(realpath ${UBLK_TEST_DIR})
142	UBLK_TMP=$(mktemp ${UBLK_TEST_DIR}/ublk_test_XXXXX)
143	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "ublk $type: $*"
144	echo "ublk selftest: $TID starting at $(date '+%F %T')" | tee /dev/kmsg
145}
146
147_remove_test_files()
148{
149	local files=$*
150
151	for file in ${files}; do
152		[ -f "${file}" ] && rm -f "${file}"
153	done
154}
155
156_show_result()
157{
158	if [ "$UBLK_TEST_SHOW_RESULT" -ne 0 ]; then
159		if [ "$2" -eq 0 ]; then
160			echo "$1 : [PASS]"
161		elif [ "$2" -eq 4 ]; then
162			echo "$1 : [SKIP]"
163		else
164			echo "$1 : [FAIL]"
165		fi
166	fi
167	if [ "$2" -ne 0 ]; then
168		_remove_files
169		exit "$2"
170	fi
171	return 0
172}
173
174# don't call from sub-shell, otherwise can't exit
175_check_add_dev()
176{
177	local tid=$1
178	local code=$2
179
180	if [ "${code}" -ne 0 ]; then
181		_show_result "${tid}" "${code}"
182	fi
183}
184
185_cleanup_test() {
186	if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
187		while read -r dev_id; do
188			${UBLK_PROG} del -n "${dev_id}"
189		done < "${UBLK_TEST_DIR}/.ublk_devs"
190		rm -f "${UBLK_TEST_DIR}/.ublk_devs"
191	fi
192
193	_remove_files
194	rmdir ${UBLK_TEST_DIR}
195	echo "ublk selftest: $TID done at $(date '+%F %T')" | tee /dev/kmsg
196}
197
198_have_feature()
199{
200	if  $UBLK_PROG "features" | grep "$1" > /dev/null 2>&1; then
201		return 0
202	fi
203	return 1
204}
205
206_create_ublk_dev() {
207	local dev_id;
208	local cmd=$1
209	local settle=$2
210
211	shift 2
212
213	if [ ! -c /dev/ublk-control ]; then
214		return ${UBLK_SKIP_CODE}
215	fi
216	if echo "$@" | grep -q "\-z"; then
217		if ! _have_feature "ZERO_COPY"; then
218			return ${UBLK_SKIP_CODE}
219		fi
220	fi
221
222	if ! dev_id=$("${UBLK_PROG}" "$cmd" "$@" | grep "dev id" | awk -F '[ :]' '{print $3}'); then
223		echo "fail to add ublk dev $*"
224		return 255
225	fi
226
227	if [ "$settle" = "yes" ]; then
228		udevadm settle --timeout=20
229	fi
230
231	if [[ "$dev_id" =~ ^[0-9]+$ ]]; then
232		echo "$dev_id" >> "${UBLK_TEST_DIR}/.ublk_devs"
233		echo "${dev_id}"
234	else
235		return 255
236	fi
237}
238
239_add_ublk_dev() {
240	_create_ublk_dev "add" "yes" "$@"
241}
242
243_add_ublk_dev_no_settle() {
244	_create_ublk_dev "add" "no" "$@"
245}
246
247_recover_ublk_dev() {
248	local dev_id
249	local state
250
251	dev_id=$(_create_ublk_dev "recover" "yes" "$@")
252	for ((j=0;j<100;j++)); do
253		state=$(_get_ublk_dev_state "${dev_id}")
254		[ "$state" == "LIVE" ] && break
255		sleep 1
256	done
257	echo "$state"
258}
259
260# quiesce device and return ublk device state
261__ublk_quiesce_dev()
262{
263	local dev_id=$1
264	local exp_state=$2
265	local state
266
267	if ! ${UBLK_PROG} quiesce -n "${dev_id}"; then
268		state=$(_get_ublk_dev_state "${dev_id}")
269		return "$state"
270	fi
271
272	for ((j=0;j<100;j++)); do
273		state=$(_get_ublk_dev_state "${dev_id}")
274		[ "$state" == "$exp_state" ] && break
275		sleep 1
276	done
277	echo "$state"
278}
279
280# kill the ublk daemon and return ublk device state
281__ublk_kill_daemon()
282{
283	local dev_id=$1
284	local exp_state=$2
285	local daemon_pid
286	local state
287
288	daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
289	state=$(_get_ublk_dev_state "${dev_id}")
290
291	for ((j=0;j<100;j++)); do
292		[ "$state" == "$exp_state" ] && break
293		kill -9 "$daemon_pid" > /dev/null 2>&1
294		sleep 1
295		state=$(_get_ublk_dev_state "${dev_id}")
296	done
297	echo "$state"
298}
299
300_ublk_del_dev() {
301	local dev_id=$1
302
303	${UBLK_PROG} del -n "${dev_id}"
304
305	# Remove from tracking file
306	if [ -f "${UBLK_TEST_DIR}/.ublk_devs" ]; then
307		sed -i "/^${dev_id}$/d" "${UBLK_TEST_DIR}/.ublk_devs"
308	fi
309}
310
311__remove_ublk_dev_return() {
312	local dev_id=$1
313
314	_ublk_del_dev "${dev_id}"
315	local res=$?
316	udevadm settle --timeout=20
317	return ${res}
318}
319
320__run_io_and_remove()
321{
322	local dev_id=$1
323	local size=$2
324	local kill_server=$3
325
326	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
327		--rw=randrw --norandommap --iodepth=256 --size="${size}" --numjobs="$(nproc)" \
328		--runtime=20 --time_based > /dev/null 2>&1 &
329	fio --name=batchjob --filename=/dev/ublkb"${dev_id}" --ioengine=io_uring \
330		--rw=randrw --norandommap --iodepth=256 --size="${size}" \
331		--numjobs="$(nproc)" --runtime=20 --time_based \
332		--iodepth_batch_submit=32 --iodepth_batch_complete_min=32 \
333		--force_async=7 > /dev/null 2>&1 &
334	sleep 2
335	if [ "${kill_server}" = "yes" ]; then
336		local state
337		state=$(__ublk_kill_daemon "${dev_id}" "DEAD")
338		if [ "$state" != "DEAD" ]; then
339			echo "device isn't dead($state) after killing daemon"
340			return 255
341		fi
342	fi
343	if ! __remove_ublk_dev_return "${dev_id}"; then
344		echo "delete dev ${dev_id} failed"
345		return 255
346	fi
347	wait
348}
349
350run_io_and_remove()
351{
352	local size=$1
353	local dev_id
354	shift 1
355
356	dev_id=$(_add_ublk_dev "$@")
357	_check_add_dev "$TID" $?
358
359	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
360	if ! __run_io_and_remove "$dev_id" "${size}" "no"; then
361		echo "/dev/ublkc$dev_id isn't removed"
362		exit 255
363	fi
364}
365
366run_io_and_kill_daemon()
367{
368	local size=$1
369	local dev_id
370	shift 1
371
372	dev_id=$(_add_ublk_dev "$@")
373	_check_add_dev "$TID" $?
374
375	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs kill ublk server(ublk add $*)"
376	if ! __run_io_and_remove "$dev_id" "${size}" "yes"; then
377		echo "/dev/ublkc$dev_id isn't removed res ${res}"
378		exit 255
379	fi
380}
381
382run_io_and_recover()
383{
384	local size=$1
385	local action=$2
386	local state
387	local dev_id
388
389	shift 2
390	dev_id=$(_add_ublk_dev "$@")
391	_check_add_dev "$TID" $?
392
393	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
394		--rw=randread --iodepth=256 --size="${size}" --numjobs=4 \
395		--runtime=20 --time_based > /dev/null 2>&1 &
396	sleep 4
397
398	if [ "$action" == "kill_daemon" ]; then
399		state=$(__ublk_kill_daemon "${dev_id}" "QUIESCED")
400	elif [ "$action" == "quiesce_dev" ]; then
401		state=$(__ublk_quiesce_dev "${dev_id}" "QUIESCED")
402	fi
403	if [ "$state" != "QUIESCED" ]; then
404		echo "device isn't quiesced($state) after $action"
405		return 255
406	fi
407
408	state=$(_recover_ublk_dev -n "$dev_id" "$@")
409	if [ "$state" != "LIVE" ]; then
410		echo "faile to recover to LIVE($state)"
411		return 255
412	fi
413
414	if ! __remove_ublk_dev_return "${dev_id}"; then
415		echo "delete dev ${dev_id} failed"
416		return 255
417	fi
418	wait
419}
420
421
422_ublk_test_top_dir()
423{
424	cd "$(dirname "$0")" && pwd
425}
426
427METADATA_SIZE_PROG="$(_ublk_test_top_dir)/metadata_size"
428
429_get_metadata_size()
430{
431	local dev_id=$1
432	local field=$2
433
434	"$METADATA_SIZE_PROG" "/dev/ublkb$dev_id" | grep "$field" | grep -o "[0-9]*"
435}
436
437UBLK_PROG=$(_ublk_test_top_dir)/kublk
438UBLK_TEST_QUIET=1
439UBLK_TEST_SHOW_RESULT=1
440UBLK_BACKFILES=()
441export UBLK_PROG
442export UBLK_TEST_QUIET
443export UBLK_TEST_SHOW_RESULT
444