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=$(stat -c '%Hr' "$dev")
21	minor=$(stat -c '%Lr' "$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_TEST_QUIET" -eq 0 ] && echo "ublk $type: $*"
118}
119
120_remove_test_files()
121{
122	local files=$*
123
124	for file in ${files}; do
125		[ -f "${file}" ] && rm -f "${file}"
126	done
127}
128
129_show_result()
130{
131	if [ "$UBLK_TEST_SHOW_RESULT" -ne 0 ]; then
132		if [ "$2" -eq 0 ]; then
133			echo "$1 : [PASS]"
134		elif [ "$2" -eq 4 ]; then
135			echo "$1 : [SKIP]"
136		else
137			echo "$1 : [FAIL]"
138		fi
139	fi
140	if [ "$2" -ne 0 ]; then
141		_remove_files
142		exit "$2"
143	fi
144	return 0
145}
146
147# don't call from sub-shell, otherwise can't exit
148_check_add_dev()
149{
150	local tid=$1
151	local code=$2
152
153	if [ "${code}" -ne 0 ]; then
154		_show_result "${tid}" "${code}"
155	fi
156}
157
158_cleanup_test() {
159	"${UBLK_PROG}" del -a
160
161	_remove_files
162}
163
164_have_feature()
165{
166	if  $UBLK_PROG "features" | grep "$1" > /dev/null 2>&1; then
167		return 0
168	fi
169	return 1
170}
171
172_add_ublk_dev() {
173	local dev_id;
174
175	if [ ! -c /dev/ublk-control ]; then
176		return ${UBLK_SKIP_CODE}
177	fi
178	if echo "$@" | grep -q "\-z"; then
179		if ! _have_feature "ZERO_COPY"; then
180			return ${UBLK_SKIP_CODE}
181		fi
182	fi
183
184	if ! dev_id=$("${UBLK_PROG}" add "$@" | grep "dev id" | awk -F '[ :]' '{print $3}'); then
185		echo "fail to add ublk dev $*"
186		return 255
187	fi
188	udevadm settle
189
190	if [[ "$dev_id" =~ ^[0-9]+$ ]]; then
191		echo "${dev_id}"
192	else
193		return 255
194	fi
195}
196
197# kill the ublk daemon and return ublk device state
198__ublk_kill_daemon()
199{
200	local dev_id=$1
201	local exp_state=$2
202	local daemon_pid
203	local state
204
205	daemon_pid=$(_get_ublk_daemon_pid "${dev_id}")
206	state=$(_get_ublk_dev_state "${dev_id}")
207
208	for ((j=0;j<50;j++)); do
209		[ "$state" == "$exp_state" ] && break
210		kill -9 "$daemon_pid" > /dev/null 2>&1
211		sleep 1
212		state=$(_get_ublk_dev_state "${dev_id}")
213	done
214	echo "$state"
215}
216
217__remove_ublk_dev_return() {
218	local dev_id=$1
219
220	${UBLK_PROG} del -n "${dev_id}"
221	local res=$?
222	udevadm settle
223	return ${res}
224}
225
226__run_io_and_remove()
227{
228	local dev_id=$1
229	local size=$2
230	local kill_server=$3
231
232	fio --name=job1 --filename=/dev/ublkb"${dev_id}" --ioengine=libaio \
233		--rw=readwrite --iodepth=256 --size="${size}" --numjobs=4 \
234		--runtime=20 --time_based > /dev/null 2>&1 &
235	sleep 2
236	if [ "${kill_server}" = "yes" ]; then
237		local state
238		state=$(__ublk_kill_daemon "${dev_id}" "DEAD")
239		if [ "$state" != "DEAD" ]; then
240			echo "device isn't dead($state) after killing daemon"
241			return 255
242		fi
243	fi
244	if ! __remove_ublk_dev_return "${dev_id}"; then
245		echo "delete dev ${dev_id} failed"
246		return 255
247	fi
248	wait
249}
250
251run_io_and_remove()
252{
253	local size=$1
254	local dev_id
255	shift 1
256
257	dev_id=$(_add_ublk_dev "$@")
258	_check_add_dev "$TID" $?
259
260	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs. remove device(ublk add $*)"
261	if ! __run_io_and_remove "$dev_id" "${size}" "no"; then
262		echo "/dev/ublkc$dev_id isn't removed"
263		exit 255
264	fi
265}
266
267run_io_and_kill_daemon()
268{
269	local size=$1
270	local dev_id
271	shift 1
272
273	dev_id=$(_add_ublk_dev "$@")
274	_check_add_dev "$TID" $?
275
276	[ "$UBLK_TEST_QUIET" -eq 0 ] && echo "run ublk IO vs kill ublk server(ublk add $*)"
277	if ! __run_io_and_remove "$dev_id" "${size}" "yes"; then
278		echo "/dev/ublkc$dev_id isn't removed res ${res}"
279		exit 255
280	fi
281}
282
283_ublk_test_top_dir()
284{
285	cd "$(dirname "$0")" && pwd
286}
287
288UBLK_TMP=$(mktemp ublk_test_XXXXX)
289UBLK_PROG=$(_ublk_test_top_dir)/kublk
290UBLK_TEST_QUIET=1
291UBLK_TEST_SHOW_RESULT=1
292UBLK_BACKFILES=()
293export UBLK_PROG
294export UBLK_TEST_QUIET
295export UBLK_TEST_SHOW_RESULT
296