forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
9.4 KiB
397 lines
9.4 KiB
#!/bin/bash |
|
# SPDX-License-Identifier: GPL-2.0 |
|
|
|
set -u |
|
set -e |
|
|
|
# This script currently only works for x86_64, as |
|
# it is based on the VM image used by the BPF CI which is |
|
# x86_64. |
|
QEMU_BINARY="${QEMU_BINARY:="qemu-system-x86_64"}" |
|
X86_BZIMAGE="arch/x86/boot/bzImage" |
|
DEFAULT_COMMAND="./test_progs" |
|
MOUNT_DIR="mnt" |
|
ROOTFS_IMAGE="root.img" |
|
OUTPUT_DIR="$HOME/.bpf_selftests" |
|
KCONFIG_URL="https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/latest.config" |
|
KCONFIG_API_URL="https://api.github.com/repos/libbpf/libbpf/contents/travis-ci/vmtest/configs/latest.config" |
|
INDEX_URL="https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/INDEX" |
|
NUM_COMPILE_JOBS="$(nproc)" |
|
LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")" |
|
LOG_FILE="${LOG_FILE_BASE}.log" |
|
EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status" |
|
|
|
usage() |
|
{ |
|
cat <<EOF |
|
Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>] |
|
|
|
<command> is the command you would normally run when you are in |
|
tools/testing/selftests/bpf. e.g: |
|
|
|
$0 -- ./test_progs -t test_lsm |
|
|
|
If no command is specified and a debug shell (-s) is not requested, |
|
"${DEFAULT_COMMAND}" will be run by default. |
|
|
|
If you build your kernel using KBUILD_OUTPUT= or O= options, these |
|
can be passed as environment variables to the script: |
|
|
|
O=<kernel_build_path> $0 -- ./test_progs -t test_lsm |
|
|
|
or |
|
|
|
KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm |
|
|
|
Options: |
|
|
|
-i) Update the rootfs image with a newer version. |
|
-d) Update the output directory (default: ${OUTPUT_DIR}) |
|
-j) Number of jobs for compilation, similar to -j in make |
|
(default: ${NUM_COMPILE_JOBS}) |
|
-s) Instead of powering off the VM, start an interactive |
|
shell. If <command> is specified, the shell runs after |
|
the command finishes executing |
|
EOF |
|
} |
|
|
|
unset URLS |
|
populate_url_map() |
|
{ |
|
if ! declare -p URLS &> /dev/null; then |
|
# URLS contain the mapping from file names to URLs where |
|
# those files can be downloaded from. |
|
declare -gA URLS |
|
while IFS=$'\t' read -r name url; do |
|
URLS["$name"]="$url" |
|
done < <(curl -Lsf ${INDEX_URL}) |
|
fi |
|
} |
|
|
|
download() |
|
{ |
|
local file="$1" |
|
|
|
if [[ ! -v URLS[$file] ]]; then |
|
echo "$file not found" >&2 |
|
return 1 |
|
fi |
|
|
|
echo "Downloading $file..." >&2 |
|
curl -Lsf "${URLS[$file]}" "${@:2}" |
|
} |
|
|
|
newest_rootfs_version() |
|
{ |
|
{ |
|
for file in "${!URLS[@]}"; do |
|
if [[ $file =~ ^libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then |
|
echo "${BASH_REMATCH[1]}" |
|
fi |
|
done |
|
} | sort -rV | head -1 |
|
} |
|
|
|
download_rootfs() |
|
{ |
|
local rootfsversion="$1" |
|
local dir="$2" |
|
|
|
if ! which zstd &> /dev/null; then |
|
echo 'Could not find "zstd" on the system, please install zstd' |
|
exit 1 |
|
fi |
|
|
|
download "libbpf-vmtest-rootfs-$rootfsversion.tar.zst" | |
|
zstd -d | sudo tar -C "$dir" -x |
|
} |
|
|
|
recompile_kernel() |
|
{ |
|
local kernel_checkout="$1" |
|
local make_command="$2" |
|
|
|
cd "${kernel_checkout}" |
|
|
|
${make_command} olddefconfig |
|
${make_command} |
|
} |
|
|
|
mount_image() |
|
{ |
|
local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" |
|
local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" |
|
|
|
sudo mount -o loop "${rootfs_img}" "${mount_dir}" |
|
} |
|
|
|
unmount_image() |
|
{ |
|
local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" |
|
|
|
sudo umount "${mount_dir}" &> /dev/null |
|
} |
|
|
|
update_selftests() |
|
{ |
|
local kernel_checkout="$1" |
|
local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf" |
|
|
|
cd "${selftests_dir}" |
|
${make_command} |
|
|
|
# Mount the image and copy the selftests to the image. |
|
mount_image |
|
sudo rm -rf "${mount_dir}/root/bpf" |
|
sudo cp -r "${selftests_dir}" "${mount_dir}/root" |
|
unmount_image |
|
} |
|
|
|
update_init_script() |
|
{ |
|
local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d" |
|
local init_script="${init_script_dir}/S50-startup" |
|
local command="$1" |
|
local exit_command="$2" |
|
|
|
mount_image |
|
|
|
if [[ ! -d "${init_script_dir}" ]]; then |
|
cat <<EOF |
|
Could not find ${init_script_dir} in the mounted image. |
|
This likely indicates a bad rootfs image, Please download |
|
a new image by passing "-i" to the script |
|
EOF |
|
exit 1 |
|
|
|
fi |
|
|
|
sudo bash -c "echo '#!/bin/bash' > ${init_script}" |
|
|
|
if [[ "${command}" != "" ]]; then |
|
sudo bash -c "cat >>${init_script}" <<EOF |
|
# Have a default value in the exit status file |
|
# incase the VM is forcefully stopped. |
|
echo "130" > "/root/${EXIT_STATUS_FILE}" |
|
|
|
{ |
|
cd /root/bpf |
|
echo ${command} |
|
stdbuf -oL -eL ${command} |
|
echo "\$?" > "/root/${EXIT_STATUS_FILE}" |
|
} 2>&1 | tee "/root/${LOG_FILE}" |
|
# Ensure that the logs are written to disk |
|
sync |
|
EOF |
|
fi |
|
|
|
sudo bash -c "echo ${exit_command} >> ${init_script}" |
|
sudo chmod a+x "${init_script}" |
|
unmount_image |
|
} |
|
|
|
create_vm_image() |
|
{ |
|
local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" |
|
local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" |
|
|
|
rm -rf "${rootfs_img}" |
|
touch "${rootfs_img}" |
|
chattr +C "${rootfs_img}" >/dev/null 2>&1 || true |
|
|
|
truncate -s 2G "${rootfs_img}" |
|
mkfs.ext4 -q "${rootfs_img}" |
|
|
|
mount_image |
|
download_rootfs "$(newest_rootfs_version)" "${mount_dir}" |
|
unmount_image |
|
} |
|
|
|
run_vm() |
|
{ |
|
local kernel_bzimage="$1" |
|
local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" |
|
|
|
if ! which "${QEMU_BINARY}" &> /dev/null; then |
|
cat <<EOF |
|
Could not find ${QEMU_BINARY} |
|
Please install qemu or set the QEMU_BINARY environment variable. |
|
EOF |
|
exit 1 |
|
fi |
|
|
|
${QEMU_BINARY} \ |
|
-nodefaults \ |
|
-display none \ |
|
-serial mon:stdio \ |
|
-cpu kvm64 \ |
|
-enable-kvm \ |
|
-smp 4 \ |
|
-m 2G \ |
|
-drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \ |
|
-kernel "${kernel_bzimage}" \ |
|
-append "root=/dev/vda rw console=ttyS0,115200" |
|
} |
|
|
|
copy_logs() |
|
{ |
|
local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" |
|
local log_file="${mount_dir}/root/${LOG_FILE}" |
|
local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}" |
|
|
|
mount_image |
|
sudo cp ${log_file} "${OUTPUT_DIR}" |
|
sudo cp ${exit_status_file} "${OUTPUT_DIR}" |
|
sudo rm -f ${log_file} |
|
unmount_image |
|
} |
|
|
|
is_rel_path() |
|
{ |
|
local path="$1" |
|
|
|
[[ ${path:0:1} != "/" ]] |
|
} |
|
|
|
update_kconfig() |
|
{ |
|
local kconfig_file="$1" |
|
local update_command="curl -sLf ${KCONFIG_URL} -o ${kconfig_file}" |
|
# Github does not return the "last-modified" header when retrieving the |
|
# raw contents of the file. Use the API call to get the last-modified |
|
# time of the kernel config and only update the config if it has been |
|
# updated after the previously cached config was created. This avoids |
|
# unnecessarily compiling the kernel and selftests. |
|
if [[ -f "${kconfig_file}" ]]; then |
|
local last_modified_date="$(curl -sL -D - "${KCONFIG_API_URL}" -o /dev/null | \ |
|
grep "last-modified" | awk -F ': ' '{print $2}')" |
|
local remote_modified_timestamp="$(date -d "${last_modified_date}" +"%s")" |
|
local local_creation_timestamp="$(stat -c %Y "${kconfig_file}")" |
|
|
|
if [[ "${remote_modified_timestamp}" -gt "${local_creation_timestamp}" ]]; then |
|
${update_command} |
|
fi |
|
else |
|
${update_command} |
|
fi |
|
} |
|
|
|
main() |
|
{ |
|
local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" |
|
local kernel_checkout=$(realpath "${script_dir}"/../../../../) |
|
# By default the script searches for the kernel in the checkout directory but |
|
# it also obeys environment variables O= and KBUILD_OUTPUT= |
|
local kernel_bzimage="${kernel_checkout}/${X86_BZIMAGE}" |
|
local command="${DEFAULT_COMMAND}" |
|
local update_image="no" |
|
local exit_command="poweroff -f" |
|
local debug_shell="no" |
|
|
|
while getopts 'hskid:j:' opt; do |
|
case ${opt} in |
|
i) |
|
update_image="yes" |
|
;; |
|
d) |
|
OUTPUT_DIR="$OPTARG" |
|
;; |
|
j) |
|
NUM_COMPILE_JOBS="$OPTARG" |
|
;; |
|
s) |
|
command="" |
|
debug_shell="yes" |
|
exit_command="bash" |
|
;; |
|
h) |
|
usage |
|
exit 0 |
|
;; |
|
\? ) |
|
echo "Invalid Option: -$OPTARG" |
|
usage |
|
exit 1 |
|
;; |
|
: ) |
|
echo "Invalid Option: -$OPTARG requires an argument" |
|
usage |
|
exit 1 |
|
;; |
|
esac |
|
done |
|
shift $((OPTIND -1)) |
|
|
|
if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then |
|
echo "No command specified, will run ${DEFAULT_COMMAND} in the vm" |
|
else |
|
command="$@" |
|
fi |
|
|
|
local kconfig_file="${OUTPUT_DIR}/latest.config" |
|
local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}" |
|
|
|
# Figure out where the kernel is being built. |
|
# O takes precedence over KBUILD_OUTPUT. |
|
if [[ "${O:=""}" != "" ]]; then |
|
if is_rel_path "${O}"; then |
|
O="$(realpath "${PWD}/${O}")" |
|
fi |
|
kernel_bzimage="${O}/${X86_BZIMAGE}" |
|
make_command="${make_command} O=${O}" |
|
elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then |
|
if is_rel_path "${KBUILD_OUTPUT}"; then |
|
KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")" |
|
fi |
|
kernel_bzimage="${KBUILD_OUTPUT}/${X86_BZIMAGE}" |
|
make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}" |
|
fi |
|
|
|
populate_url_map |
|
|
|
local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}" |
|
local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}" |
|
|
|
echo "Output directory: ${OUTPUT_DIR}" |
|
|
|
mkdir -p "${OUTPUT_DIR}" |
|
mkdir -p "${mount_dir}" |
|
update_kconfig "${kconfig_file}" |
|
|
|
recompile_kernel "${kernel_checkout}" "${make_command}" |
|
|
|
if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then |
|
echo "rootfs image not found in ${rootfs_img}" |
|
update_image="yes" |
|
fi |
|
|
|
if [[ "${update_image}" == "yes" ]]; then |
|
create_vm_image |
|
fi |
|
|
|
update_selftests "${kernel_checkout}" "${make_command}" |
|
update_init_script "${command}" "${exit_command}" |
|
run_vm "${kernel_bzimage}" |
|
if [[ "${command}" != "" ]]; then |
|
copy_logs |
|
echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}" |
|
fi |
|
} |
|
|
|
catch() |
|
{ |
|
local exit_code=$1 |
|
local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}" |
|
# This is just a cleanup and the directory may |
|
# have already been unmounted. So, don't let this |
|
# clobber the error code we intend to return. |
|
unmount_image || true |
|
if [[ -f "${exit_status_file}" ]]; then |
|
exit_code="$(cat ${exit_status_file})" |
|
fi |
|
exit ${exit_code} |
|
} |
|
|
|
trap 'catch "$?"' EXIT |
|
|
|
main "$@"
|
|
|