1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4UBLK_SKIP_CODE=4
5
6_have_program() {
7	if command -v "$1" >/dev/null 2>&1; then
8		return 0
9	fi
10	return 1
11}
12
13_get_disk_dev_t() {
14	local dev_id=$1
15	local dev
16	local major
17	local minor
18
19	dev=/dev/ublkb"${dev_id}"
20	major="0x"$(stat -c '%t' "$dev")
21	minor="0x"$(stat -c '%T' "$dev")
22
23	echo $(( (major & 0xfff) << 20 | (minor & 0xfffff) ))
24}
25
26_run_fio_verify_io() {
27	fio --name=verify --rw=randwrite --direct=1 --ioengine=libaio \
28		--bs=8k --iodepth=32 --verify=crc32c --do_verify=1 \
29		--verify_state_save=0 "$@" > /dev/null
30}
31
32_create_backfile() {
33	local index=$1
34	local new_size=$2
35	local old_file
36	local new_file
37
38	old_file="${UBLK_BACKFILES[$index]}"
39	[ -f "$old_file" ] && rm -f "$old_file"
40
41	new_file=$(mktemp ublk_file_"${new_size}"_XXXXX)
42	truncate -s "${new_size}" "${new_file}"
43	UBLK_BACKFILES["$index"]="$new_file"
44}
45
46_remove_files() {
47	local file
48
49	for file in "${UBLK_BACKFILES[@]}"; do
50		[ -f "$file" ] && rm -f "$file"
51	done
52	[ -f "$UBLK_TMP" ] && rm -f "$UBLK_TMP"
53}
54
55_create_tmp_dir() {
56	local my_file;
57
58	my_file=$(mktemp -d ublk_dir_XXXXX)
59	echo "$my_file"
60}
61
62_remove_tmp_dir() {
63	local dir=$1
64
65	[ -d "$dir" ] && rmdir "$dir"
66}
67
68_mkfs_mount_test()
69{
70	local dev=$1
71	local err_code=0
72	local mnt_dir;
73
74	mnt_dir=$(_create_tmp_dir)
75	mkfs.ext4 -F "$dev" > /dev/null 2>&1
76	err_code=$?
77	if [ $err_code -ne 0 ]; then
78		return $err_code
79	fi
80
81	mount -t ext4 "$dev" "$mnt_dir" > /dev/null 2>&1
82	umount "$dev"
83	err_code=$?
84	_remove_tmp_dir "$mnt_dir"
85	if [ $err_code -ne 0 ]; then
86		return $err_code
87	fi
88}
89
90_check_root() {
91	local ksft_skip=4
92
93	if [ $UID != 0 ]; then
94		echo please run this as root >&2
95		exit $ksft_skip
96	fi
97}
98
99_remove_ublk_devices() {
100	${UBLK_PROG} del -a
101	modprobe -r ublk_drv > /dev/null 2>&1
102}
103
104_get_ublk_dev_state() {
105	${UBLK_PROG} list -n "$1" | grep "state" | awk '{print $11}'
106}
107
108_get_ublk_daemon_pid() {
109	${UBLK_PROG} list -n "$1" | grep "pid" | awk '{print $7}'
110}
111
112_prep_test() {
113	_check_root
114	local type=$1
115	shift 1
116	modprobe ublk_drv > /dev/null 2>&1
117	UBLK_TMP=$(mktemp ublk_test_XXXXX)
118	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "ublk $type: $*"
119}
120
121_remove_test_files()
122{
123	local files=$*
124
125	for file in ${files}; do
126		[ -f "${file}" ] && rm -f "${file}"
127	done
128}
129
130_show_result()
131{
132	if [ "$UBLK_TEST_SHOW_RESULT" -ne 0 ]; then
133		if [ "$2" -eq 0 ]; then
134			echo "$1 : [PASS]"
135		elif [ "$2" -eq 4 ]; then
136			echo "$1 : [SKIP]"
137		else
138			echo "$1 : [FAIL]"
139		fi
140	fi
141	if [ "$2" -ne 0 ]; then
142		_remove_files
143		exit "$2"
144	fi
145	return 0
146}
147
148# don't call from sub-shell, otherwise can't exit
149_check_add_dev()
150{
151	local tid=$1
152	local code=$2
153
154	if [ "${code}" -ne 0 ]; then
155		_show_result "${tid}" "${code}"
156	fi
157}
158
159_cleanup_test() {
160	"${UBLK_PROG}" del -a
161
162	_remove_files
163}
164
165_have_feature()
166{
167	if  $UBLK_PROG "features" | grep "$1" > /dev/null 2>&1; then
168		return 0
169	fi
170	return 1
171}
172
173_create_ublk_dev() {
174	local dev_id;
175	local cmd=$1
176
177	shift 1
178
179	if [ ! -c /dev/ublk-control ]; then
180		return ${UBLK_SKIP_CODE}
181	fi
182	if echo "$@" | grep -q "\-z"; then
183		if ! _have_feature "ZERO_COPY"; then
184			return ${UBLK_SKIP_CODE}
185		fi
186	fi
187
188	if ! dev_id=$("${UBLK_PROG}" "$cmd" "$@" | grep "dev id" | awk -F '[ :]' '{print $3}'); then
189		echo "fail to add ublk dev $*"
190		return 255
191	fi
192	udevadm settle
193
194	if [[ "$dev_id" =~ ^[0-9]+$ ]]; then
195		echo "${dev_id}"
196	else
197		return 255
198	fi
199}
200
201_add_ublk_dev() {
202	_create_ublk_dev "add" "$@"
203}
204
205_recover_ublk_dev() {
206	local dev_id
207	local state
208
209	dev_id=$(_create_ublk_dev "recover" "$@")
210	for ((j=0;j<20;j++)); do
211		state=$(_get_ublk_dev_state "${dev_id}")
212		[ "$state" == "LIVE" ] && break
213		sleep 1
214	done
215	echo "$state"
216}
217
218# kill the ublk daemon and return ublk device state
219__ublk_kill_daemon()
220{
221	local dev_id=$1
222	local exp_state=$2
223	local daemon_pid
224	local state
225
226	daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
227	state=$(_get_ublk_dev_state "${dev_id}")
228
229	for ((j=0;j<50;j++)); do
230		[ "$state" == "$exp_state" ] && break
231		kill -9 "$daemon_pid" > /dev/null 2>&1
232		sleep 1
233		state=$(_get_ublk_dev_state "${dev_id}")
234	done
235	echo "$state"
236}
237
238__remove_ublk_dev_return() {
239	local dev_id=$1
240
241	${UBLK_PROG} del -n "${dev_id}"
242	local res=$?
243	udevadm settle
244	return ${res}
245}
246
247__run_io_and_remove()
248{
249	local dev_id=$1
250	local size=$2
251	local kill_server=$3
252
253	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
254		--rw=readwrite --iodepth=256 --size="${size}" --numjobs=4 \
255		--runtime=20 --time_based > /dev/null 2>&1 &
256	sleep 2
257	if [ "${kill_server}" = "yes" ]; then
258		local state
259		state=$(__ublk_kill_daemon "${dev_id}" "DEAD")
260		if [ "$state" != "DEAD" ]; then
261			echo "device isn't dead($state) after killing daemon"
262			return 255
263		fi
264	fi
265	if ! __remove_ublk_dev_return "${dev_id}"; then
266		echo "delete dev ${dev_id} failed"
267		return 255
268	fi
269	wait
270}
271
272run_io_and_remove()
273{
274	local size=$1
275	local dev_id
276	shift 1
277
278	dev_id=$(_add_ublk_dev "$@")
279	_check_add_dev "$TID" $?
280
281	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
282	if ! __run_io_and_remove "$dev_id" "${size}" "no"; then
283		echo "/dev/ublkc$dev_id isn't removed"
284		exit 255
285	fi
286}
287
288run_io_and_kill_daemon()
289{
290	local size=$1
291	local dev_id
292	shift 1
293
294	dev_id=$(_add_ublk_dev "$@")
295	_check_add_dev "$TID" $?
296
297	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs kill ublk server(ublk add $*)"
298	if ! __run_io_and_remove "$dev_id" "${size}" "yes"; then
299		echo "/dev/ublkc$dev_id isn't removed res ${res}"
300		exit 255
301	fi
302}
303
304run_io_and_recover()
305{
306	local state
307	local dev_id
308
309	dev_id=$(_add_ublk_dev "$@")
310	_check_add_dev "$TID" $?
311
312	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
313		--rw=readwrite --iodepth=256 --size="${size}" --numjobs=4 \
314		--runtime=20 --time_based > /dev/null 2>&1 &
315	sleep 4
316
317	state=$(__ublk_kill_daemon "${dev_id}" "QUIESCED")
318	if [ "$state" != "QUIESCED" ]; then
319		echo "device isn't quiesced($state) after killing daemon"
320		return 255
321	fi
322
323	state=$(_recover_ublk_dev -n "$dev_id" "$@")
324	if [ "$state" != "LIVE" ]; then
325		echo "faile to recover to LIVE($state)"
326		return 255
327	fi
328
329	if ! __remove_ublk_dev_return "${dev_id}"; then
330		echo "delete dev ${dev_id} failed"
331		return 255
332	fi
333	wait
334}
335
336
337_ublk_test_top_dir()
338{
339	cd "$(dirname "$0")" && pwd
340}
341
342UBLK_PROG=$(_ublk_test_top_dir)/kublk
343UBLK_TEST_QUIET=1
344UBLK_TEST_SHOW_RESULT=1
345UBLK_BACKFILES=()
346export UBLK_PROG
347export UBLK_TEST_QUIET
348export UBLK_TEST_SHOW_RESULT
349