| 1 | |
| 2 | |
| 3 |
|
| 4 | set -e
|
| 5 |
|
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | PLATFORM="${PLATFORM:-$(uname -m)}"
|
| 12 | case "${PLATFORM}" in
|
| 13 | s390x)
|
| 14 | QEMU_BINARY=qemu-system-s390x
|
| 15 | QEMU_CONSOLE="ttyS1"
|
| 16 | HOST_FLAGS=(-smp 2 -enable-kvm)
|
| 17 | CROSS_FLAGS=(-smp 2)
|
| 18 | BZIMAGE="arch/s390/boot/vmlinux"
|
| 19 | ARCH="s390"
|
| 20 | ;;
|
| 21 | x86_64)
|
| 22 | QEMU_BINARY=qemu-system-x86_64
|
| 23 | QEMU_CONSOLE="ttyS0,115200"
|
| 24 | HOST_FLAGS=(-cpu host -enable-kvm -smp 8)
|
| 25 | CROSS_FLAGS=(-smp 8)
|
| 26 | BZIMAGE="arch/x86/boot/bzImage"
|
| 27 | ARCH="x86"
|
| 28 | ;;
|
| 29 | aarch64)
|
| 30 | QEMU_BINARY=qemu-system-aarch64
|
| 31 | QEMU_CONSOLE="ttyAMA0,115200"
|
| 32 | HOST_FLAGS=(-M virt,gic-version=3 -cpu host -enable-kvm -smp 8)
|
| 33 | CROSS_FLAGS=(-M virt,gic-version=3 -cpu cortex-a76 -smp 8)
|
| 34 | BZIMAGE="arch/arm64/boot/Image"
|
| 35 | ARCH="arm64"
|
| 36 | ;;
|
| 37 | riscv64)
|
| 38 | |
| 39 | QEMU_BINARY=qemu-system-riscv64
|
| 40 | QEMU_CONSOLE="ttyS0,115200"
|
| 41 | HOST_FLAGS=(-M virt -cpu host -enable-kvm -smp 8)
|
| 42 | CROSS_FLAGS=(-M virt -cpu rv64,sscofpmf=true -smp 8)
|
| 43 | BZIMAGE="arch/riscv/boot/Image"
|
| 44 | ARCH="riscv"
|
| 45 | ;;
|
| 46 | ppc64el)
|
| 47 | QEMU_BINARY=qemu-system-ppc64
|
| 48 | QEMU_CONSOLE="hvc0"
|
| 49 | |
| 50 | HOST_FLAGS=(-machine pseries -cpu POWER9)
|
| 51 | CROSS_FLAGS=(-machine pseries -cpu POWER9)
|
| 52 | BZIMAGE="vmlinux"
|
| 53 | ARCH="powerpc"
|
| 54 | ;;
|
| 55 | *)
|
| 56 | echo "Unsupported architecture"
|
| 57 | exit 1
|
| 58 | ;;
|
| 59 | esac
|
| 60 | DEFAULT_COMMAND="./test_progs"
|
| 61 | MOUNT_DIR="mnt"
|
| 62 | LOCAL_ROOTFS_IMAGE=""
|
| 63 | ROOTFS_IMAGE="root.img"
|
| 64 | OUTPUT_DIR="$HOME/.bpf_selftests"
|
| 65 | KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config"
|
| 66 | "tools/testing/selftests/bpf/config.vm"
|
| 67 | "tools/testing/selftests/bpf/config.${PLATFORM}")
|
| 68 | INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
|
| 69 | NUM_COMPILE_JOBS="$(nproc)"
|
| 70 | LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
|
| 71 | LOG_FILE="${LOG_FILE_BASE}.log"
|
| 72 | EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
|
| 73 |
|
| 74 | usage()
|
| 75 | {
|
| 76 | cat <<EOF
|
| 77 | Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
|
| 78 |
|
| 79 | <command> is the command you would normally run when you are in
|
| 80 | tools/testing/selftests/bpf. e.g:
|
| 81 |
|
| 82 | $0 -- ./test_progs -t test_lsm
|
| 83 |
|
| 84 | If no command is specified and a debug shell (-s) is not requested,
|
| 85 | "${DEFAULT_COMMAND}" will be run by default.
|
| 86 |
|
| 87 | Using PLATFORM= and CROSS_COMPILE= options will enable cross platform testing:
|
| 88 |
|
| 89 | PLATFORM=<platform> CROSS_COMPILE=<toolchain> $0 -- ./test_progs -t test_lsm
|
| 90 |
|
| 91 | If you build your kernel using KBUILD_OUTPUT= or O= options, these
|
| 92 | can be passed as environment variables to the script:
|
| 93 |
|
| 94 | O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
|
| 95 |
|
| 96 | or
|
| 97 |
|
| 98 | KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
|
| 99 |
|
| 100 | Options:
|
| 101 |
|
| 102 | -l) Specify the path to the local rootfs image.
|
| 103 | -i) Update the rootfs image with a newer version.
|
| 104 | -d) Update the output directory (default: ${OUTPUT_DIR})
|
| 105 | -j) Number of jobs for compilation, similar to -j in make
|
| 106 | (default: ${NUM_COMPILE_JOBS})
|
| 107 | -s) Instead of powering off the VM, start an interactive
|
| 108 | shell. If <command> is specified, the shell runs after
|
| 109 | the command finishes executing
|
| 110 | EOF
|
| 111 | }
|
| 112 |
|
| 113 | unset URLS
|
| 114 | populate_url_map()
|
| 115 | {
|
| 116 | if ! declare -p URLS &> /dev/null; then
|
| 117 | |
| 118 | |
| 119 | declare -gA URLS
|
| 120 | while IFS=$'\t' read -r name url; do
|
| 121 | URLS["$name"]="$url"
|
| 122 | done < <(curl -Lsf ${INDEX_URL})
|
| 123 | fi
|
| 124 | }
|
| 125 |
|
| 126 | newest_rootfs_version()
|
| 127 | {
|
| 128 | {
|
| 129 | for file in "${!URLS[@]}"; do
|
| 130 | if [[ $file =~ ^"${PLATFORM}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
|
| 131 | echo "${BASH_REMATCH[1]}"
|
| 132 | fi
|
| 133 | done
|
| 134 | } | sort -rV | head -1
|
| 135 | }
|
| 136 |
|
| 137 | download_rootfs()
|
| 138 | {
|
| 139 | populate_url_map
|
| 140 |
|
| 141 | local rootfsversion="$(newest_rootfs_version)"
|
| 142 | local file="${PLATFORM}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst"
|
| 143 |
|
| 144 | if [[ ! -v URLS[$file] ]]; then
|
| 145 | echo "$file not found" >&2
|
| 146 | return 1
|
| 147 | fi
|
| 148 |
|
| 149 | echo "Downloading $file..." >&2
|
| 150 | curl -Lsf "${URLS[$file]}" "${@:2}"
|
| 151 | }
|
| 152 |
|
| 153 | load_rootfs()
|
| 154 | {
|
| 155 | local dir="$1"
|
| 156 |
|
| 157 | if ! which zstd &> /dev/null; then
|
| 158 | echo 'Could not find "zstd" on the system, please install zstd'
|
| 159 | exit 1
|
| 160 | fi
|
| 161 |
|
| 162 | if [[ -n "${LOCAL_ROOTFS_IMAGE}" ]]; then
|
| 163 | cat "${LOCAL_ROOTFS_IMAGE}" | zstd -d | sudo tar -C "$dir" -x
|
| 164 | else
|
| 165 | download_rootfs | zstd -d | sudo tar -C "$dir" -x
|
| 166 | fi
|
| 167 | }
|
| 168 |
|
| 169 | recompile_kernel()
|
| 170 | {
|
| 171 | local kernel_checkout="$1"
|
| 172 | local make_command="$2"
|
| 173 |
|
| 174 | cd "${kernel_checkout}"
|
| 175 |
|
| 176 | ${make_command} olddefconfig
|
| 177 | ${make_command}
|
| 178 | }
|
| 179 |
|
| 180 | mount_image()
|
| 181 | {
|
| 182 | local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
|
| 183 | local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
|
| 184 |
|
| 185 | sudo mount -o loop "${rootfs_img}" "${mount_dir}"
|
| 186 | }
|
| 187 |
|
| 188 | unmount_image()
|
| 189 | {
|
| 190 | local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
|
| 191 |
|
| 192 | sudo umount "${mount_dir}" &> /dev/null
|
| 193 | }
|
| 194 |
|
| 195 | update_selftests()
|
| 196 | {
|
| 197 | local kernel_checkout="$1"
|
| 198 | local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
|
| 199 |
|
| 200 | cd "${selftests_dir}"
|
| 201 | ${make_command}
|
| 202 |
|
| 203 | |
| 204 | mount_image
|
| 205 | sudo rm -rf "${mount_dir}/root/bpf"
|
| 206 | sudo cp -r "${selftests_dir}" "${mount_dir}/root"
|
| 207 | unmount_image
|
| 208 | }
|
| 209 |
|
| 210 | update_init_script()
|
| 211 | {
|
| 212 | local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
|
| 213 | local init_script="${init_script_dir}/S50-startup"
|
| 214 | local command="$1"
|
| 215 | local exit_command="$2"
|
| 216 |
|
| 217 | mount_image
|
| 218 |
|
| 219 | if [[ ! -d "${init_script_dir}" ]]; then
|
| 220 | cat <<EOF
|
| 221 | Could not find ${init_script_dir} in the mounted image.
|
| 222 | This likely indicates a bad rootfs image, Please download
|
| 223 | a new image by passing "-i" to the script
|
| 224 | EOF
|
| 225 | exit 1
|
| 226 |
|
| 227 | fi
|
| 228 |
|
| 229 | sudo bash -c "echo '#!/bin/bash' > ${init_script}"
|
| 230 |
|
| 231 | if [[ "${command}" != "" ]]; then
|
| 232 | sudo bash -c "cat >>${init_script}" <<EOF
|
| 233 | # Have a default value in the exit status file
|
| 234 | # incase the VM is forcefully stopped.
|
| 235 | echo "130" > "/root/${EXIT_STATUS_FILE}"
|
| 236 |
|
| 237 | {
|
| 238 | cd /root/bpf
|
| 239 | echo ${command}
|
| 240 | stdbuf -oL -eL ${command}
|
| 241 | echo "\$?" > "/root/${EXIT_STATUS_FILE}"
|
| 242 | } 2>&1 | tee "/root/${LOG_FILE}"
|
| 243 | # Ensure that the logs are written to disk
|
| 244 | sync
|
| 245 | EOF
|
| 246 | fi
|
| 247 |
|
| 248 | sudo bash -c "echo ${exit_command} >> ${init_script}"
|
| 249 | sudo chmod a+x "${init_script}"
|
| 250 | unmount_image
|
| 251 | }
|
| 252 |
|
| 253 | create_vm_image()
|
| 254 | {
|
| 255 | local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
|
| 256 | local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
|
| 257 |
|
| 258 | rm -rf "${rootfs_img}"
|
| 259 | touch "${rootfs_img}"
|
| 260 | chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
|
| 261 |
|
| 262 | truncate -s 2G "${rootfs_img}"
|
| 263 | mkfs.ext4 -q "${rootfs_img}"
|
| 264 |
|
| 265 | mount_image
|
| 266 | load_rootfs "${mount_dir}"
|
| 267 | unmount_image
|
| 268 | }
|
| 269 |
|
| 270 | run_vm()
|
| 271 | {
|
| 272 | local kernel_bzimage="$1"
|
| 273 | local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
|
| 274 |
|
| 275 | if ! which "${QEMU_BINARY}" &> /dev/null; then
|
| 276 | cat <<EOF
|
| 277 | Could not find ${QEMU_BINARY}
|
| 278 | Please install qemu or set the QEMU_BINARY environment variable.
|
| 279 | EOF
|
| 280 | exit 1
|
| 281 | fi
|
| 282 |
|
| 283 | if [[ "${PLATFORM}" != "$(uname -m)" ]]; then
|
| 284 | QEMU_FLAGS=("${CROSS_FLAGS[@]}")
|
| 285 | else
|
| 286 | QEMU_FLAGS=("${HOST_FLAGS[@]}")
|
| 287 | fi
|
| 288 |
|
| 289 | ${QEMU_BINARY} \
|
| 290 | -nodefaults \
|
| 291 | -display none \
|
| 292 | -serial mon:stdio \
|
| 293 | "${QEMU_FLAGS[@]}" \
|
| 294 | -m 4G \
|
| 295 | -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
|
| 296 | -kernel "${kernel_bzimage}" \
|
| 297 | -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
|
| 298 | }
|
| 299 |
|
| 300 | copy_logs()
|
| 301 | {
|
| 302 | local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
|
| 303 | local log_file="${mount_dir}/root/${LOG_FILE}"
|
| 304 | local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
|
| 305 |
|
| 306 | mount_image
|
| 307 | sudo cp ${log_file} "${OUTPUT_DIR}"
|
| 308 | sudo cp ${exit_status_file} "${OUTPUT_DIR}"
|
| 309 | sudo rm -f ${log_file}
|
| 310 | unmount_image
|
| 311 | }
|
| 312 |
|
| 313 | is_rel_path()
|
| 314 | {
|
| 315 | local path="$1"
|
| 316 |
|
| 317 | [[ ${path:0:1} != "/" ]]
|
| 318 | }
|
| 319 |
|
| 320 | do_update_kconfig()
|
| 321 | {
|
| 322 | local kernel_checkout="$1"
|
| 323 | local kconfig_file="$2"
|
| 324 |
|
| 325 | rm -f "$kconfig_file" 2> /dev/null
|
| 326 |
|
| 327 | for config in "${KCONFIG_REL_PATHS[@]}"; do
|
| 328 | local kconfig_src="${kernel_checkout}/${config}"
|
| 329 | cat "$kconfig_src" >> "$kconfig_file"
|
| 330 | done
|
| 331 | }
|
| 332 |
|
| 333 | update_kconfig()
|
| 334 | {
|
| 335 | local kernel_checkout="$1"
|
| 336 | local kconfig_file="$2"
|
| 337 |
|
| 338 | if [[ -f "${kconfig_file}" ]]; then
|
| 339 | local local_modified="$(stat -c %Y "${kconfig_file}")"
|
| 340 |
|
| 341 | for config in "${KCONFIG_REL_PATHS[@]}"; do
|
| 342 | local kconfig_src="${kernel_checkout}/${config}"
|
| 343 | local src_modified="$(stat -c %Y "${kconfig_src}")"
|
| 344 | |
| 345 | |
| 346 | |
| 347 | if [[ "${src_modified}" -gt "${local_modified}" ]]; then
|
| 348 | do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
| 349 | |
| 350 | |
| 351 | break
|
| 352 | fi
|
| 353 | done
|
| 354 | else
|
| 355 | do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
| 356 | fi
|
| 357 | }
|
| 358 |
|
| 359 | catch()
|
| 360 | {
|
| 361 | local exit_code=$1
|
| 362 | local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
|
| 363 | |
| 364 | |
| 365 | |
| 366 | unmount_image || true
|
| 367 | if [[ -f "${exit_status_file}" ]]; then
|
| 368 | exit_code="$(cat ${exit_status_file})"
|
| 369 | fi
|
| 370 | exit ${exit_code}
|
| 371 | }
|
| 372 |
|
| 373 | main()
|
| 374 | {
|
| 375 | local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
| 376 | local kernel_checkout=$(realpath "${script_dir}"/../../../../)
|
| 377 | |
| 378 | |
| 379 | local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
|
| 380 | local command="${DEFAULT_COMMAND}"
|
| 381 | local update_image="no"
|
| 382 | local exit_command="poweroff -f"
|
| 383 | local debug_shell="no"
|
| 384 |
|
| 385 | while getopts ':hsl:id:j:' opt; do
|
| 386 | case ${opt} in
|
| 387 | l)
|
| 388 | LOCAL_ROOTFS_IMAGE="$OPTARG"
|
| 389 | ;;
|
| 390 | i)
|
| 391 | update_image="yes"
|
| 392 | ;;
|
| 393 | d)
|
| 394 | OUTPUT_DIR="$OPTARG"
|
| 395 | ;;
|
| 396 | j)
|
| 397 | NUM_COMPILE_JOBS="$OPTARG"
|
| 398 | ;;
|
| 399 | s)
|
| 400 | command=""
|
| 401 | debug_shell="yes"
|
| 402 | exit_command="bash"
|
| 403 | ;;
|
| 404 | h)
|
| 405 | usage
|
| 406 | exit 0
|
| 407 | ;;
|
| 408 | \? )
|
| 409 | echo "Invalid Option: -$OPTARG"
|
| 410 | usage
|
| 411 | exit 1
|
| 412 | ;;
|
| 413 | : )
|
| 414 | echo "Invalid Option: -$OPTARG requires an argument"
|
| 415 | usage
|
| 416 | exit 1
|
| 417 | ;;
|
| 418 | esac
|
| 419 | done
|
| 420 | shift $((OPTIND -1))
|
| 421 |
|
| 422 | trap 'catch "$?"' EXIT
|
| 423 |
|
| 424 | if [[ "${PLATFORM}" != "$(uname -m)" ]] && [[ -z "${CROSS_COMPILE}" ]]; then
|
| 425 | echo "Cross-platform testing needs to specify CROSS_COMPILE"
|
| 426 | exit 1
|
| 427 | fi
|
| 428 |
|
| 429 | if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
|
| 430 | echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
|
| 431 | elif [[ $# -gt 0 ]]; then
|
| 432 | |
| 433 | |
| 434 | |
| 435 | |
| 436 | |
| 437 | |
| 438 | |
| 439 | |
| 440 | |
| 441 | command=$(printf '%q ' "$@")
|
| 442 | fi
|
| 443 |
|
| 444 | local kconfig_file="${OUTPUT_DIR}/latest.config"
|
| 445 | local make_command="make ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} \
|
| 446 | -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
|
| 447 |
|
| 448 | |
| 449 | |
| 450 | if [[ "${O:=""}" != "" ]]; then
|
| 451 | if is_rel_path "${O}"; then
|
| 452 | O="$(realpath "${PWD}/${O}")"
|
| 453 | fi
|
| 454 | kernel_bzimage="${O}/${BZIMAGE}"
|
| 455 | make_command="${make_command} O=${O}"
|
| 456 | elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
|
| 457 | if is_rel_path "${KBUILD_OUTPUT}"; then
|
| 458 | KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
|
| 459 | fi
|
| 460 | kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
|
| 461 | make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
|
| 462 | fi
|
| 463 |
|
| 464 | local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
|
| 465 | local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
|
| 466 |
|
| 467 | echo "Output directory: ${OUTPUT_DIR}"
|
| 468 |
|
| 469 | mkdir -p "${OUTPUT_DIR}"
|
| 470 | mkdir -p "${mount_dir}"
|
| 471 | update_kconfig "${kernel_checkout}" "${kconfig_file}"
|
| 472 |
|
| 473 | recompile_kernel "${kernel_checkout}" "${make_command}"
|
| 474 |
|
| 475 | if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
|
| 476 | echo "rootfs image not found in ${rootfs_img}"
|
| 477 | update_image="yes"
|
| 478 | fi
|
| 479 |
|
| 480 | if [[ "${update_image}" == "yes" ]]; then
|
| 481 | create_vm_image
|
| 482 | fi
|
| 483 |
|
| 484 | update_selftests "${kernel_checkout}" "${make_command}"
|
| 485 | update_init_script "${command}" "${exit_command}"
|
| 486 | run_vm "${kernel_bzimage}"
|
| 487 | if [[ "${command}" != "" ]]; then
|
| 488 | copy_logs
|
| 489 | echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
|
| 490 | fi
|
| 491 | }
|
| 492 |
|
| 493 | main "$@"
|