2 # SPDX-License-Identifier: GPL-2.0
7 # This script currently only works for x86_64 and s390x, as
8 # it is based on the VM image used by the BPF CI, which is
9 # available only for these architectures.
13 QEMU_BINARY=qemu-system-s390x
16 BZIMAGE="arch/s390/boot/compressed/vmlinux"
19 QEMU_BINARY=qemu-system-x86_64
20 QEMU_CONSOLE="ttyS0,115200"
21 QEMU_FLAGS=(-cpu host -smp 8)
22 BZIMAGE="arch/x86/boot/bzImage"
25 QEMU_BINARY=qemu-system-aarch64
26 QEMU_CONSOLE="ttyAMA0,115200"
27 QEMU_FLAGS=(-M virt,gic-version=3 -cpu host -smp 8)
28 BZIMAGE="arch/arm64/boot/Image"
31 echo "Unsupported architecture"
35 DEFAULT_COMMAND="./test_progs"
37 ROOTFS_IMAGE="root.img"
38 OUTPUT_DIR="$HOME/.bpf_selftests"
39 KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config" "tools/testing/selftests/bpf/config.${ARCH}")
40 INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
41 NUM_COMPILE_JOBS="$(nproc)"
42 LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
43 LOG_FILE="${LOG_FILE_BASE}.log"
44 EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
49 Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
51 <command> is the command you would normally run when you are in
52 tools/testing/selftests/bpf. e.g:
54 $0 -- ./test_progs -t test_lsm
56 If no command is specified and a debug shell (-s) is not requested,
57 "${DEFAULT_COMMAND}" will be run by default.
59 If you build your kernel using KBUILD_OUTPUT= or O= options, these
60 can be passed as environment variables to the script:
62 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
66 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
70 -i) Update the rootfs image with a newer version.
71 -d) Update the output directory (default: ${OUTPUT_DIR})
72 -j) Number of jobs for compilation, similar to -j in make
73 (default: ${NUM_COMPILE_JOBS})
74 -s) Instead of powering off the VM, start an interactive
75 shell. If <command> is specified, the shell runs after
76 the command finishes executing
83 if ! declare -p URLS &> /dev/null; then
84 # URLS contain the mapping from file names to URLs where
85 # those files can be downloaded from.
87 while IFS=$'\t' read -r name url; do
89 done < <(curl -Lsf ${INDEX_URL})
97 if [[ ! -v URLS[$file] ]]; then
98 echo "$file not found" >&2
102 echo "Downloading $file..." >&2
103 curl -Lsf "${URLS[$file]}" "${@:2}"
106 newest_rootfs_version()
109 for file in "${!URLS[@]}"; do
110 if [[ $file =~ ^"${ARCH}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
111 echo "${BASH_REMATCH[1]}"
114 } | sort -rV | head -1
119 local rootfsversion="$1"
122 if ! which zstd &> /dev/null; then
123 echo 'Could not find "zstd" on the system, please install zstd'
127 download "${ARCH}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst" |
128 zstd -d | sudo tar -C "$dir" -x
133 local kernel_checkout="$1"
134 local make_command="$2"
136 cd "${kernel_checkout}"
138 ${make_command} olddefconfig
144 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
145 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
147 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
152 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
154 sudo umount "${mount_dir}" &> /dev/null
159 local kernel_checkout="$1"
160 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
162 cd "${selftests_dir}"
165 # Mount the image and copy the selftests to the image.
167 sudo rm -rf "${mount_dir}/root/bpf"
168 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
174 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
175 local init_script="${init_script_dir}/S50-startup"
177 local exit_command="$2"
181 if [[ ! -d "${init_script_dir}" ]]; then
183 Could not find ${init_script_dir} in the mounted image.
184 This likely indicates a bad rootfs image, Please download
185 a new image by passing "-i" to the script
191 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
193 if [[ "${command}" != "" ]]; then
194 sudo bash -c "cat >>${init_script}" <<EOF
195 # Have a default value in the exit status file
196 # incase the VM is forcefully stopped.
197 echo "130" > "/root/${EXIT_STATUS_FILE}"
202 stdbuf -oL -eL ${command}
203 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
204 } 2>&1 | tee "/root/${LOG_FILE}"
205 # Ensure that the logs are written to disk
210 sudo bash -c "echo ${exit_command} >> ${init_script}"
211 sudo chmod a+x "${init_script}"
217 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
218 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
220 rm -rf "${rootfs_img}"
221 touch "${rootfs_img}"
222 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
224 truncate -s 2G "${rootfs_img}"
225 mkfs.ext4 -q "${rootfs_img}"
228 download_rootfs "$(newest_rootfs_version)" "${mount_dir}"
234 local kernel_bzimage="$1"
235 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
237 if ! which "${QEMU_BINARY}" &> /dev/null; then
239 Could not find ${QEMU_BINARY}
240 Please install qemu or set the QEMU_BINARY environment variable.
252 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
253 -kernel "${kernel_bzimage}" \
254 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
259 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
260 local log_file="${mount_dir}/root/${LOG_FILE}"
261 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
264 sudo cp ${log_file} "${OUTPUT_DIR}"
265 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
266 sudo rm -f ${log_file}
274 [[ ${path:0:1} != "/" ]]
279 local kernel_checkout="$1"
280 local kconfig_file="$2"
282 rm -f "$kconfig_file" 2> /dev/null
284 for config in "${KCONFIG_REL_PATHS[@]}"; do
285 local kconfig_src="${kernel_checkout}/${config}"
286 cat "$kconfig_src" >> "$kconfig_file"
292 local kernel_checkout="$1"
293 local kconfig_file="$2"
295 if [[ -f "${kconfig_file}" ]]; then
296 local local_modified="$(stat -c %Y "${kconfig_file}")"
298 for config in "${KCONFIG_REL_PATHS[@]}"; do
299 local kconfig_src="${kernel_checkout}/${config}"
300 local src_modified="$(stat -c %Y "${kconfig_src}")"
301 # Only update the config if it has been updated after the
302 # previously cached config was created. This avoids
303 # unnecessarily compiling the kernel and selftests.
304 if [[ "${src_modified}" -gt "${local_modified}" ]]; then
305 do_update_kconfig "$kernel_checkout" "$kconfig_file"
306 # Once we have found one outdated configuration
307 # there is no need to check other ones.
312 do_update_kconfig "$kernel_checkout" "$kconfig_file"
319 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
320 # This is just a cleanup and the directory may
321 # have already been unmounted. So, don't let this
322 # clobber the error code we intend to return.
323 unmount_image || true
324 if [[ -f "${exit_status_file}" ]]; then
325 exit_code="$(cat ${exit_status_file})"
332 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
333 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
334 # By default the script searches for the kernel in the checkout directory but
335 # it also obeys environment variables O= and KBUILD_OUTPUT=
336 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
337 local command="${DEFAULT_COMMAND}"
338 local update_image="no"
339 local exit_command="poweroff -f"
340 local debug_shell="no"
342 while getopts ':hskid:j:' opt; do
351 NUM_COMPILE_JOBS="$OPTARG"
363 echo "Invalid Option: -$OPTARG"
368 echo "Invalid Option: -$OPTARG requires an argument"
376 trap 'catch "$?"' EXIT
378 if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
379 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
384 local kconfig_file="${OUTPUT_DIR}/latest.config"
385 local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
387 # Figure out where the kernel is being built.
388 # O takes precedence over KBUILD_OUTPUT.
389 if [[ "${O:=""}" != "" ]]; then
390 if is_rel_path "${O}"; then
391 O="$(realpath "${PWD}/${O}")"
393 kernel_bzimage="${O}/${BZIMAGE}"
394 make_command="${make_command} O=${O}"
395 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
396 if is_rel_path "${KBUILD_OUTPUT}"; then
397 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
399 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
400 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
405 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
406 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
408 echo "Output directory: ${OUTPUT_DIR}"
410 mkdir -p "${OUTPUT_DIR}"
411 mkdir -p "${mount_dir}"
412 update_kconfig "${kernel_checkout}" "${kconfig_file}"
414 recompile_kernel "${kernel_checkout}" "${make_command}"
416 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
417 echo "rootfs image not found in ${rootfs_img}"
421 if [[ "${update_image}" == "yes" ]]; then
425 update_selftests "${kernel_checkout}" "${make_command}"
426 update_init_script "${command}" "${exit_command}"
427 run_vm "${kernel_bzimage}"
428 if [[ "${command}" != "" ]]; then
430 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"