0001
0002
0003
0004 set -u
0005 set -e
0006
0007
0008
0009
0010 ARCH="$(uname -m)"
0011 case "${ARCH}" in
0012 s390x)
0013 QEMU_BINARY=qemu-system-s390x
0014 QEMU_CONSOLE="ttyS1"
0015 QEMU_FLAGS=(-smp 2)
0016 BZIMAGE="arch/s390/boot/compressed/vmlinux"
0017 ;;
0018 x86_64)
0019 QEMU_BINARY=qemu-system-x86_64
0020 QEMU_CONSOLE="ttyS0,115200"
0021 QEMU_FLAGS=(-cpu host -smp 8)
0022 BZIMAGE="arch/x86/boot/bzImage"
0023 ;;
0024 *)
0025 echo "Unsupported architecture"
0026 exit 1
0027 ;;
0028 esac
0029 DEFAULT_COMMAND="./test_progs"
0030 MOUNT_DIR="mnt"
0031 ROOTFS_IMAGE="root.img"
0032 OUTPUT_DIR="$HOME/.bpf_selftests"
0033 KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config" "tools/testing/selftests/bpf/config.${ARCH}")
0034 INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
0035 NUM_COMPILE_JOBS="$(nproc)"
0036 LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
0037 LOG_FILE="${LOG_FILE_BASE}.log"
0038 EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
0039
0040 usage()
0041 {
0042 cat <<EOF
0043 Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
0044
0045 <command> is the command you would normally run when you are in
0046 tools/testing/selftests/bpf. e.g:
0047
0048 $0 -- ./test_progs -t test_lsm
0049
0050 If no command is specified and a debug shell (-s) is not requested,
0051 "${DEFAULT_COMMAND}" will be run by default.
0052
0053 If you build your kernel using KBUILD_OUTPUT= or O= options, these
0054 can be passed as environment variables to the script:
0055
0056 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
0057
0058 or
0059
0060 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
0061
0062 Options:
0063
0064 -i) Update the rootfs image with a newer version.
0065 -d) Update the output directory (default: ${OUTPUT_DIR})
0066 -j) Number of jobs for compilation, similar to -j in make
0067 (default: ${NUM_COMPILE_JOBS})
0068 -s) Instead of powering off the VM, start an interactive
0069 shell. If <command> is specified, the shell runs after
0070 the command finishes executing
0071 EOF
0072 }
0073
0074 unset URLS
0075 populate_url_map()
0076 {
0077 if ! declare -p URLS &> /dev/null; then
0078
0079
0080 declare -gA URLS
0081 while IFS=$'\t' read -r name url; do
0082 URLS["$name"]="$url"
0083 done < <(curl -Lsf ${INDEX_URL})
0084 fi
0085 }
0086
0087 download()
0088 {
0089 local file="$1"
0090
0091 if [[ ! -v URLS[$file] ]]; then
0092 echo "$file not found" >&2
0093 return 1
0094 fi
0095
0096 echo "Downloading $file..." >&2
0097 curl -Lsf "${URLS[$file]}" "${@:2}"
0098 }
0099
0100 newest_rootfs_version()
0101 {
0102 {
0103 for file in "${!URLS[@]}"; do
0104 if [[ $file =~ ^"${ARCH}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
0105 echo "${BASH_REMATCH[1]}"
0106 fi
0107 done
0108 } | sort -rV | head -1
0109 }
0110
0111 download_rootfs()
0112 {
0113 local rootfsversion="$1"
0114 local dir="$2"
0115
0116 if ! which zstd &> /dev/null; then
0117 echo 'Could not find "zstd" on the system, please install zstd'
0118 exit 1
0119 fi
0120
0121 download "${ARCH}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst" |
0122 zstd -d | sudo tar -C "$dir" -x
0123 }
0124
0125 recompile_kernel()
0126 {
0127 local kernel_checkout="$1"
0128 local make_command="$2"
0129
0130 cd "${kernel_checkout}"
0131
0132 ${make_command} olddefconfig
0133 ${make_command}
0134 }
0135
0136 mount_image()
0137 {
0138 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
0139 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
0140
0141 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
0142 }
0143
0144 unmount_image()
0145 {
0146 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
0147
0148 sudo umount "${mount_dir}" &> /dev/null
0149 }
0150
0151 update_selftests()
0152 {
0153 local kernel_checkout="$1"
0154 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
0155
0156 cd "${selftests_dir}"
0157 ${make_command}
0158
0159
0160 mount_image
0161 sudo rm -rf "${mount_dir}/root/bpf"
0162 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
0163 unmount_image
0164 }
0165
0166 update_init_script()
0167 {
0168 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
0169 local init_script="${init_script_dir}/S50-startup"
0170 local command="$1"
0171 local exit_command="$2"
0172
0173 mount_image
0174
0175 if [[ ! -d "${init_script_dir}" ]]; then
0176 cat <<EOF
0177 Could not find ${init_script_dir} in the mounted image.
0178 This likely indicates a bad rootfs image, Please download
0179 a new image by passing "-i" to the script
0180 EOF
0181 exit 1
0182
0183 fi
0184
0185 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
0186
0187 if [[ "${command}" != "" ]]; then
0188 sudo bash -c "cat >>${init_script}" <<EOF
0189
0190
0191 echo "130" > "/root/${EXIT_STATUS_FILE}"
0192
0193 {
0194 cd /root/bpf
0195 echo ${command}
0196 stdbuf -oL -eL ${command}
0197 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
0198 } 2>&1 | tee "/root/${LOG_FILE}"
0199
0200 sync
0201 EOF
0202 fi
0203
0204 sudo bash -c "echo ${exit_command} >> ${init_script}"
0205 sudo chmod a+x "${init_script}"
0206 unmount_image
0207 }
0208
0209 create_vm_image()
0210 {
0211 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
0212 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
0213
0214 rm -rf "${rootfs_img}"
0215 touch "${rootfs_img}"
0216 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
0217
0218 truncate -s 2G "${rootfs_img}"
0219 mkfs.ext4 -q "${rootfs_img}"
0220
0221 mount_image
0222 download_rootfs "$(newest_rootfs_version)" "${mount_dir}"
0223 unmount_image
0224 }
0225
0226 run_vm()
0227 {
0228 local kernel_bzimage="$1"
0229 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
0230
0231 if ! which "${QEMU_BINARY}" &> /dev/null; then
0232 cat <<EOF
0233 Could not find ${QEMU_BINARY}
0234 Please install qemu or set the QEMU_BINARY environment variable.
0235 EOF
0236 exit 1
0237 fi
0238
0239 ${QEMU_BINARY} \
0240 -nodefaults \
0241 -display none \
0242 -serial mon:stdio \
0243 "${QEMU_FLAGS[@]}" \
0244 -enable-kvm \
0245 -m 4G \
0246 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
0247 -kernel "${kernel_bzimage}" \
0248 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
0249 }
0250
0251 copy_logs()
0252 {
0253 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
0254 local log_file="${mount_dir}/root/${LOG_FILE}"
0255 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
0256
0257 mount_image
0258 sudo cp ${log_file} "${OUTPUT_DIR}"
0259 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
0260 sudo rm -f ${log_file}
0261 unmount_image
0262 }
0263
0264 is_rel_path()
0265 {
0266 local path="$1"
0267
0268 [[ ${path:0:1} != "/" ]]
0269 }
0270
0271 do_update_kconfig()
0272 {
0273 local kernel_checkout="$1"
0274 local kconfig_file="$2"
0275
0276 rm -f "$kconfig_file" 2> /dev/null
0277
0278 for config in "${KCONFIG_REL_PATHS[@]}"; do
0279 local kconfig_src="${kernel_checkout}/${config}"
0280 cat "$kconfig_src" >> "$kconfig_file"
0281 done
0282 }
0283
0284 update_kconfig()
0285 {
0286 local kernel_checkout="$1"
0287 local kconfig_file="$2"
0288
0289 if [[ -f "${kconfig_file}" ]]; then
0290 local local_modified="$(stat -c %Y "${kconfig_file}")"
0291
0292 for config in "${KCONFIG_REL_PATHS[@]}"; do
0293 local kconfig_src="${kernel_checkout}/${config}"
0294 local src_modified="$(stat -c %Y "${kconfig_src}")"
0295
0296
0297
0298 if [[ "${src_modified}" -gt "${local_modified}" ]]; then
0299 do_update_kconfig "$kernel_checkout" "$kconfig_file"
0300
0301
0302 break
0303 fi
0304 done
0305 else
0306 do_update_kconfig "$kernel_checkout" "$kconfig_file"
0307 fi
0308 }
0309
0310 main()
0311 {
0312 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
0313 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
0314
0315
0316 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
0317 local command="${DEFAULT_COMMAND}"
0318 local update_image="no"
0319 local exit_command="poweroff -f"
0320 local debug_shell="no"
0321
0322 while getopts 'hskid:j:' opt; do
0323 case ${opt} in
0324 i)
0325 update_image="yes"
0326 ;;
0327 d)
0328 OUTPUT_DIR="$OPTARG"
0329 ;;
0330 j)
0331 NUM_COMPILE_JOBS="$OPTARG"
0332 ;;
0333 s)
0334 command=""
0335 debug_shell="yes"
0336 exit_command="bash"
0337 ;;
0338 h)
0339 usage
0340 exit 0
0341 ;;
0342 \? )
0343 echo "Invalid Option: -$OPTARG"
0344 usage
0345 exit 1
0346 ;;
0347 : )
0348 echo "Invalid Option: -$OPTARG requires an argument"
0349 usage
0350 exit 1
0351 ;;
0352 esac
0353 done
0354 shift $((OPTIND -1))
0355
0356 if [[ $
0357 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
0358 else
0359 command="$@"
0360 fi
0361
0362 local kconfig_file="${OUTPUT_DIR}/latest.config"
0363 local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
0364
0365
0366
0367 if [[ "${O:=""}" != "" ]]; then
0368 if is_rel_path "${O}"; then
0369 O="$(realpath "${PWD}/${O}")"
0370 fi
0371 kernel_bzimage="${O}/${BZIMAGE}"
0372 make_command="${make_command} O=${O}"
0373 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
0374 if is_rel_path "${KBUILD_OUTPUT}"; then
0375 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
0376 fi
0377 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
0378 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
0379 fi
0380
0381 populate_url_map
0382
0383 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
0384 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
0385
0386 echo "Output directory: ${OUTPUT_DIR}"
0387
0388 mkdir -p "${OUTPUT_DIR}"
0389 mkdir -p "${mount_dir}"
0390 update_kconfig "${kernel_checkout}" "${kconfig_file}"
0391
0392 recompile_kernel "${kernel_checkout}" "${make_command}"
0393
0394 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
0395 echo "rootfs image not found in ${rootfs_img}"
0396 update_image="yes"
0397 fi
0398
0399 if [[ "${update_image}" == "yes" ]]; then
0400 create_vm_image
0401 fi
0402
0403 update_selftests "${kernel_checkout}" "${make_command}"
0404 update_init_script "${command}" "${exit_command}"
0405 run_vm "${kernel_bzimage}"
0406 if [[ "${command}" != "" ]]; then
0407 copy_logs
0408 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
0409 fi
0410 }
0411
0412 catch()
0413 {
0414 local exit_code=$1
0415 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
0416
0417
0418
0419 unmount_image || true
0420 if [[ -f "${exit_status_file}" ]]; then
0421 exit_code="$(cat ${exit_status_file})"
0422 fi
0423 exit ${exit_code}
0424 }
0425
0426 trap 'catch "$?"' EXIT
0427
0428 main "$@"