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