2021-02-04 22:45:43 +03:00
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
set -u
set -e
2021-11-18 14:52:25 +03:00
# This script currently only works for x86_64 and s390x, as
# it is based on the VM image used by the BPF CI, which is
# available only for these architectures.
ARCH = " $( uname -m) "
case " ${ ARCH } " in
s390x)
QEMU_BINARY = qemu-system-s390x
QEMU_CONSOLE = "ttyS1"
QEMU_FLAGS = ( -smp 2)
BZIMAGE = "arch/s390/boot/compressed/vmlinux"
; ;
x86_64)
QEMU_BINARY = qemu-system-x86_64
QEMU_CONSOLE = "ttyS0,115200"
QEMU_FLAGS = ( -cpu host -smp 8)
BZIMAGE = "arch/x86/boot/bzImage"
; ;
*)
echo "Unsupported architecture"
exit 1
; ;
esac
2021-02-04 22:45:43 +03:00
DEFAULT_COMMAND = "./test_progs"
MOUNT_DIR = "mnt"
ROOTFS_IMAGE = "root.img"
OUTPUT_DIR = " $HOME /.bpf_selftests "
2021-11-18 14:52:25 +03:00
KCONFIG_URL = " https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/config-latest. ${ ARCH } "
KCONFIG_API_URL = " https://api.github.com/repos/libbpf/libbpf/contents/travis-ci/vmtest/configs/config-latest. ${ ARCH } "
2021-12-20 08:08:03 +03:00
INDEX_URL = "https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
2021-02-04 22:45:43 +03:00
NUM_COMPILE_JOBS = " $( nproc) "
2021-02-25 19:19:47 +03:00
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 "
2021-02-04 22:45:43 +03:00
usage( )
{
cat <<EOF
2021-03-23 04:47:52 +03:00
Usage: $0 [ -i] [ -s] [ -d <output_dir>] -- [ <command>]
2021-02-04 22:45:43 +03:00
<command> is the command you would normally run when you are in
tools/testing/selftests/bpf. e.g:
$0 -- ./test_progs -t test_lsm
2021-03-23 04:47:52 +03:00
If no command is specified and a debug shell ( -s) is not requested,
" ${ DEFAULT_COMMAND } " will be run by default.
2021-02-04 22:45:43 +03:00
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 } )
2021-03-23 04:47:52 +03:00
-s) Instead of powering off the VM, start an interactive
shell. If <command> is specified, the shell runs after
the command finishes executing
2021-02-04 22:45:43 +03:00
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
2021-11-18 14:52:25 +03:00
if [ [ $file = ~ ^" ${ ARCH } " /libbpf-vmtest-rootfs-( .*) \. tar\. zst$ ] ] ; then
2021-02-04 22:45:43 +03:00
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
2021-11-18 14:52:25 +03:00
download " ${ ARCH } /libbpf-vmtest-rootfs- $rootfsversion .tar.zst " |
2021-02-04 22:45:43 +03:00
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 "
2021-03-23 04:47:52 +03:00
local exit_command = " $2 "
2021-02-04 22:45:43 +03:00
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
2021-03-23 04:47:52 +03:00
sudo bash -c " echo '#!/bin/bash' > ${ init_script } "
2021-02-04 22:45:43 +03:00
2021-03-23 04:47:52 +03:00
if [ [ " ${ command } " != "" ] ] ; then
sudo bash -c " cat >> ${ init_script } " <<EOF
2021-02-25 19:19:47 +03:00
# Have a default value in the exit status file
# incase the VM is forcefully stopped.
echo "130" > " /root/ ${ EXIT_STATUS_FILE } "
2021-02-04 22:45:43 +03:00
{
cd /root/bpf
echo ${ command }
stdbuf -oL -eL ${ command }
2021-02-25 19:19:47 +03:00
echo "\$?" > " /root/ ${ EXIT_STATUS_FILE } "
} 2>& 1 | tee " /root/ ${ LOG_FILE } "
2021-03-23 04:47:52 +03:00
# Ensure that the logs are written to disk
sync
2021-02-04 22:45:43 +03:00
EOF
2021-03-23 04:47:52 +03:00
fi
2021-02-04 22:45:43 +03:00
2021-03-23 04:47:52 +03:00
sudo bash -c " echo ${ exit_command } >> ${ init_script } "
2021-02-04 22:45:43 +03:00
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 \
2022-02-17 18:52:12 +03:00
" ${ QEMU_FLAGS [@] } " \
2021-02-04 22:45:43 +03:00
-enable-kvm \
2021-10-26 01:33:42 +03:00
-m 4G \
2021-02-04 22:45:43 +03:00
-drive file = " ${ rootfs_img } " ,format= raw,index= 1,media= disk,if= virtio,cache= none \
-kernel " ${ kernel_bzimage } " \
2021-11-18 14:52:25 +03:00
-append " root=/dev/vda rw console= ${ QEMU_CONSOLE } "
2021-02-04 22:45:43 +03:00
}
copy_logs( )
{
local mount_dir = " ${ OUTPUT_DIR } / ${ MOUNT_DIR } "
2021-02-25 19:19:47 +03:00
local log_file = " ${ mount_dir } /root/ ${ LOG_FILE } "
local exit_status_file = " ${ mount_dir } /root/ ${ EXIT_STATUS_FILE } "
2021-02-04 22:45:43 +03:00
mount_image
sudo cp ${ log_file } " ${ OUTPUT_DIR } "
2021-02-25 19:19:47 +03:00
sudo cp ${ exit_status_file } " ${ OUTPUT_DIR } "
2021-02-04 22:45:43 +03:00
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=
2021-11-18 14:52:25 +03:00
local kernel_bzimage = " ${ kernel_checkout } / ${ BZIMAGE } "
2021-02-04 22:45:43 +03:00
local command = " ${ DEFAULT_COMMAND } "
local update_image = "no"
2021-03-23 04:47:52 +03:00
local exit_command = "poweroff -f"
local debug_shell = "no"
2021-02-04 22:45:43 +03:00
2021-03-23 04:47:52 +03:00
while getopts 'hskid:j:' opt; do
2021-02-04 22:45:43 +03:00
case ${ opt } in
i)
update_image = "yes"
; ;
d)
OUTPUT_DIR = " $OPTARG "
; ;
j)
NUM_COMPILE_JOBS = " $OPTARG "
; ;
2021-03-23 04:47:52 +03:00
s)
command = ""
debug_shell = "yes"
exit_command = "bash"
; ;
2021-02-04 22:45:43 +03:00
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 ))
2021-03-23 04:47:52 +03:00
if [ [ $# -eq 0 && " ${ debug_shell } " = = "no" ] ] ; then
2021-02-04 22:45:43 +03:00
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
2021-11-18 14:52:25 +03:00
kernel_bzimage = " ${ O } / ${ BZIMAGE } "
2021-02-04 22:45:43 +03:00
make_command = " ${ make_command } O= ${ O } "
elif [ [ " ${ KBUILD_OUTPUT : = "" } " != "" ] ] ; then
if is_rel_path " ${ KBUILD_OUTPUT } " ; then
KBUILD_OUTPUT = " $( realpath " ${ PWD } / ${ KBUILD_OUTPUT } " ) "
fi
2021-11-18 14:52:25 +03:00
kernel_bzimage = " ${ KBUILD_OUTPUT } / ${ BZIMAGE } "
2021-02-04 22:45:43 +03:00
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 } "
2021-03-23 04:47:52 +03:00
update_init_script " ${ command } " " ${ exit_command } "
2021-02-04 22:45:43 +03:00
run_vm " ${ kernel_bzimage } "
2021-03-23 04:47:52 +03:00
if [ [ " ${ command } " != "" ] ] ; then
copy_logs
echo " Logs saved in ${ OUTPUT_DIR } / ${ LOG_FILE } "
fi
2021-02-04 22:45:43 +03:00
}
catch( )
{
local exit_code = $1
2021-02-25 19:19:47 +03:00
local exit_status_file = " ${ OUTPUT_DIR } / ${ EXIT_STATUS_FILE } "
2021-02-04 22:45:43 +03:00
# 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
2021-02-25 19:19:47 +03:00
if [ [ -f " ${ exit_status_file } " ] ] ; then
exit_code = " $( cat ${ exit_status_file } ) "
fi
2021-02-04 22:45:43 +03:00
exit ${ exit_code }
}
trap 'catch "$?"' EXIT
main " $@ "