Back to home page

OSCL-LXR

 
 

    


0001 #!/bin/bash
0002 # SPDX-License-Identifier: GPL-2.0
0003 
0004 set -u
0005 set -e
0006 
0007 # This script currently only works for x86_64 and s390x, as
0008 # it is based on the VM image used by the BPF CI, which is
0009 # available only for these architectures.
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                 # URLS contain the mapping from file names to URLs where
0079                 # those files can be downloaded from.
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         # Mount the image and copy the selftests to the image.
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 # Have a default value in the exit status file
0190 # incase the VM is forcefully stopped.
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 # Ensure that the logs are written to disk
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                         # Only update the config if it has been updated after the
0296                         # previously cached config was created. This avoids
0297                         # unnecessarily compiling the kernel and selftests.
0298                         if [[ "${src_modified}" -gt "${local_modified}" ]]; then
0299                                 do_update_kconfig "$kernel_checkout" "$kconfig_file"
0300                                 # Once we have found one outdated configuration
0301                                 # there is no need to check other ones.
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         # By default the script searches for the kernel in the checkout directory but
0315         # it also obeys environment variables O= and KBUILD_OUTPUT=
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 [[ $# -eq 0  && "${debug_shell}" == "no" ]]; then
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         # Figure out where the kernel is being built.
0366         # O takes precedence over KBUILD_OUTPUT.
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         # This is just a cleanup and the directory may
0417         # have already been unmounted. So, don't let this
0418         # clobber the error code we intend to return.
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 "$@"