vmtest: don't use BusyBox

We don't specifically need BusyBox; we just need a reasonable Linux
userspace, which we can assume is already available on the host, whether
it's coreutils+util-linux, BusyBox, or something else.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2022-08-01 11:22:18 -07:00
parent 1b8d0ae82b
commit b535b8f82e
4 changed files with 64 additions and 67 deletions

View File

@ -21,7 +21,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install busybox-static libelf-dev libdw-dev qemu-kvm zstd ${{ matrix.cc == 'clang' && 'libomp-$(clang --version | sed -rn "s/.*clang version ([0-9]+).*/\\1/p")-dev' || '' }} sudo apt-get install libelf-dev libdw-dev qemu-kvm zstd ${{ matrix.cc == 'clang' && 'libomp-$(clang --version | sed -rn "s/.*clang version ([0-9]+).*/\\1/p")-dev' || '' }}
pip install pyroute2 pre-commit pip install pyroute2 pre-commit
- name: Generate version.py - name: Generate version.py
run: python setup.py --version run: python setup.py --version

View File

@ -258,10 +258,10 @@ class test(Command):
set -e set -e
export DRGN_TEST_KMOD={shlex.quote(str(kmod))} export DRGN_TEST_KMOD={shlex.quote(str(kmod))}
if "$BUSYBOX" [ -e /proc/vmcore ]; then if [ -e /proc/vmcore ]; then
"$PYTHON" -Bm unittest discover -t . -s tests/linux_kernel/vmcore {"-v" if self.verbose else ""} "$PYTHON" -Bm unittest discover -t . -s tests/linux_kernel/vmcore {"-v" if self.verbose else ""}
else else
"$BUSYBOX" insmod "$DRGN_TEST_KMOD" insmod "$DRGN_TEST_KMOD"
DRGN_RUN_LINUX_KERNEL_TESTS=1 "$PYTHON" -Bm \ DRGN_RUN_LINUX_KERNEL_TESTS=1 "$PYTHON" -Bm \
unittest discover -t . -s tests/linux_kernel {"-v" if self.verbose else ""} unittest discover -t . -s tests/linux_kernel {"-v" if self.verbose else ""}
"$PYTHON" vmtest/enter_kdump.py "$PYTHON" vmtest/enter_kdump.py

View File

@ -4,8 +4,8 @@ drgn VM Testing
drgn has a significant amount of code (both core and in helpers) which is drgn has a significant amount of code (both core and in helpers) which is
dependent on the Linux kernel version. This code is tested on multiple Linux dependent on the Linux kernel version. This code is tested on multiple Linux
kernel versions in a virtual machine. These tests can be run on all supported kernel versions in a virtual machine. These tests can be run on all supported
kernels with ``python3 setup.py test -K``. This requires QEMU, BusyBox, and kernels with ``python3 setup.py test -K``. This requires QEMU and zstd to be
zstd to be installed. installed.
Tests can also be run on specific kernels with ``-k``. This takes a Tests can also be run on specific kernels with ``-k``. This takes a
comma-separated list of kernels which are wildcard patterns (e.g., ``5.6.*``) comma-separated list of kernels which are wildcard patterns (e.g., ``5.6.*``)
@ -25,9 +25,8 @@ safety. To support modifications, the guest uses `OverlayFS
overlay a read-write tmpfs over the VirtFS root. It also mounts the kernel overlay a read-write tmpfs over the VirtFS root. It also mounts the kernel
modules and vmlinux via VirtFS. modules and vmlinux via VirtFS.
The guest runs a `BusyBox <https://www.busybox.net/>`_ shell script as init The guest runs an init shell script which sets up the system and filesystem
which sets up the system and filesystem hierarchy, runs a command, and returns hierarchy, runs a command, and returns the exit status via `virtio-serial
the exit status via `virtio-serial
<https://fedoraproject.org/wiki/Features/VirtioSerial>`_. <https://fedoraproject.org/wiki/Features/VirtioSerial>`_.
This infrastructure is all generic. The drgn-specific parts are: This infrastructure is all generic. The drgn-specific parts are:

View File

@ -1,12 +1,10 @@
# Copyright (c) Meta Platforms, Inc. and affiliates. # Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import errno
import os import os
from pathlib import Path from pathlib import Path
import re import re
import shlex import shlex
import shutil
import socket import socket
import subprocess import subprocess
import sys import sys
@ -16,110 +14,114 @@ from util import nproc, out_of_date
_9PFS_MSIZE = 1024 * 1024 _9PFS_MSIZE = 1024 * 1024
# Script run as init in the virtual machine. This only depends on busybox. We # Script run as init in the virtual machine.
# don't assume that any regular commands are built in (not even echo or test), _INIT_TEMPLATE = r"""#!/bin/sh
# so we always explicitly run busybox.
_INIT_TEMPLATE = r"""#!{busybox} sh # Having /proc from the host visible in the guest can confuse some commands. In
# particular, if BusyBox is configured with FEATURE_SH_STANDALONE, then busybox
# sh executes BusyBox applets using /proc/self/exe. So, before doing anything
# else, mount /proc (using the fully qualified executable path so that BusyBox
# doesn't use /proc/self/exe).
/bin/mount -t proc -o nosuid,nodev,noexec proc /proc
set -eu set -eu
export BUSYBOX={busybox} export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PYTHON={python} export PYTHON={python}
{kdump_needs_nosmp} {kdump_needs_nosmp}
trap '"$BUSYBOX" poweroff -f' EXIT trap 'poweroff -f' EXIT
umask 022 umask 022
HOSTNAME=vmtest HOSTNAME=vmtest
VPORT_NAME=com.osandov.vmtest.0 VPORT_NAME=com.osandov.vmtest.0
RELEASE=$("$BUSYBOX" uname -r) RELEASE=$(uname -r)
# Set up overlayfs on the temporary directory containing this script. # Set up overlayfs on the temporary directory containing this script.
mnt=$("$BUSYBOX" dirname "$0") mnt=$(dirname "$0")
"$BUSYBOX" mount -t tmpfs tmpfs "$mnt" mount -t tmpfs tmpfs "$mnt"
"$BUSYBOX" mkdir "$mnt/upper" "$mnt/work" "$mnt/merged" mkdir "$mnt/upper" "$mnt/work" "$mnt/merged"
"$BUSYBOX" mkdir "$mnt/upper/dev" "$mnt/upper/etc" "$mnt/upper/mnt" mkdir "$mnt/upper/dev" "$mnt/upper/etc" "$mnt/upper/mnt"
"$BUSYBOX" mkdir -m 555 "$mnt/upper/proc" "$mnt/upper/sys" mkdir -m 555 "$mnt/upper/proc" "$mnt/upper/sys"
"$BUSYBOX" mkdir -m 1777 "$mnt/upper/tmp" mkdir -m 1777 "$mnt/upper/tmp"
# Create configuration files. mount -t overlay -o lowerdir=/,upperdir="$mnt/upper",workdir="$mnt/work" overlay "$mnt/merged"
"$BUSYBOX" cat << EOF > "$mnt/upper/etc/hosts"
127.0.0.1 localhost
::1 localhost
127.0.1.1 $HOSTNAME.localdomain $HOSTNAME
EOF
: > "$mnt/upper/etc/resolv.conf"
"$BUSYBOX" mount -t overlay -o lowerdir=/,upperdir="$mnt/upper",workdir="$mnt/work" overlay "$mnt/merged" # Mount core filesystems.
"$BUSYBOX" pivot_root "$mnt/merged" "$mnt/merged/mnt" mount -t devtmpfs -o nosuid,noexec dev "$mnt/merged/dev"
cd / mkdir "$mnt/merged/dev/shm"
"$BUSYBOX" umount -l /mnt mount -t tmpfs -o nosuid,nodev tmpfs "$mnt/merged/dev/shm"
mount -t proc -o nosuid,nodev,noexec proc "$mnt/merged/proc"
# Mount additional filesystems. mount -t sysfs -o nosuid,nodev,noexec sys "$mnt/merged/sys"
"$BUSYBOX" mount -t devtmpfs -o nosuid,noexec dev /dev
"$BUSYBOX" mkdir /dev/shm
"$BUSYBOX" mount -t tmpfs -o nosuid,nodev tmpfs /dev/shm
"$BUSYBOX" mount -t proc -o nosuid,nodev,noexec proc /proc
"$BUSYBOX" mount -t sysfs -o nosuid,nodev,noexec sys /sys
# cgroup2 was added in Linux v4.5. # cgroup2 was added in Linux v4.5.
"$BUSYBOX" mount -t cgroup2 -o nosuid,nodev,noexec cgroup2 /sys/fs/cgroup || "$BUSYBOX" true mount -t cgroup2 -o nosuid,nodev,noexec cgroup2 "$mnt/merged/sys/fs/cgroup" || true
# Ideally we'd just be able to create an opaque directory for /tmp on the upper # Ideally we'd just be able to create an opaque directory for /tmp on the upper
# layer. However, before Linux kernel commit 51f7e52dc943 ("ovl: share inode # layer. However, before Linux kernel commit 51f7e52dc943 ("ovl: share inode
# for hard link") (in v4.8), overlayfs doesn't handle hard links correctly, # for hard link") (in v4.8), overlayfs doesn't handle hard links correctly,
# which breaks some tests. # which breaks some tests.
"$BUSYBOX" mount -t tmpfs -o nosuid,nodev tmpfs /tmp mount -t tmpfs -o nosuid,nodev tmpfs "$mnt/merged/tmp"
# Pivot into the new root.
pivot_root "$mnt/merged" "$mnt/merged/mnt"
cd /
umount -l /mnt
# Load kernel modules. # Load kernel modules.
"$BUSYBOX" mkdir -p "/lib/modules/$RELEASE" mkdir -p "/lib/modules/$RELEASE"
"$BUSYBOX" mount -t 9p -o trans=virtio,cache=loose,ro,msize={_9PFS_MSIZE} modules "/lib/modules/$RELEASE" mount -t 9p -o trans=virtio,cache=loose,ro,msize={_9PFS_MSIZE} modules "/lib/modules/$RELEASE"
for module in configs rng_core virtio_rng; do for module in configs rng_core virtio_rng; do
"$BUSYBOX" modprobe "$module" modprobe "$module"
done done
# Create static device nodes. # Create static device nodes.
"$BUSYBOX" grep -v '^#' "/lib/modules/$RELEASE/modules.devname" | grep -v '^#' "/lib/modules/$RELEASE/modules.devname" |
while read -r module name node; do while read -r module name node; do
name="/dev/$name" name="/dev/$name"
dev=${{node#?}} dev=${{node#?}}
major=${{dev%%:*}} major=${{dev%%:*}}
minor=${{dev##*:}} minor=${{dev##*:}}
type=${{node%"${{dev}}"}} type=${{node%"${{dev}}"}}
"$BUSYBOX" mkdir -p "$("$BUSYBOX" dirname "$name")" mkdir -p "$(dirname "$name")"
"$BUSYBOX" mknod "$name" "$type" "$major" "$minor" mknod "$name" "$type" "$major" "$minor"
done done
"$BUSYBOX" ln -s /proc/self/fd /dev/fd ln -s /proc/self/fd /dev/fd
"$BUSYBOX" ln -s /proc/self/fd/0 /dev/stdin ln -s /proc/self/fd/0 /dev/stdin
"$BUSYBOX" ln -s /proc/self/fd/1 /dev/stdout ln -s /proc/self/fd/1 /dev/stdout
"$BUSYBOX" ln -s /proc/self/fd/2 /dev/stderr ln -s /proc/self/fd/2 /dev/stderr
# Configure networking. # Configure networking.
"$BUSYBOX" hostname "$HOSTNAME" cat << EOF > /etc/hosts
"$BUSYBOX" ip link set lo up 127.0.0.1 localhost
::1 localhost
127.0.1.1 $HOSTNAME.localdomain $HOSTNAME
EOF
: > /etc/resolv.conf
hostname "$HOSTNAME"
ip link set lo up
# Find virtio port. # Find virtio port.
vport= vport=
for vport_dir in /sys/class/virtio-ports/*; do for vport_dir in /sys/class/virtio-ports/*; do
if "$BUSYBOX" [ -r "$vport_dir/name" \ if [ -r "$vport_dir/name" -a "$(cat "$vport_dir/name")" = "$VPORT_NAME" ]; then
-a "$("$BUSYBOX" cat "$vport_dir/name")" = "$VPORT_NAME" ]; then
vport="${{vport_dir#/sys/class/virtio-ports/}}" vport="${{vport_dir#/sys/class/virtio-ports/}}"
break break
fi fi
done done
if "$BUSYBOX" [ -z "$vport" ]; then if [ -z "$vport" ]; then
"$BUSYBOX" echo "could not find virtio-port \"$VPORT_NAME\"" echo "could not find virtio-port \"$VPORT_NAME\""
exit 1 exit 1
fi fi
cd {cwd} cd {cwd}
set +e set +e
"$BUSYBOX" sh -c {command} sh -c {command}
rc=$? rc=$?
set -e set -e
"$BUSYBOX" echo "Exited with status $rc" echo "Exited with status $rc"
"$BUSYBOX" echo "$rc" > "/dev/$vport" echo "$rc" > "/dev/$vport"
""" """
@ -208,15 +210,11 @@ def run_in_vm(command: str, kernel_dir: Path, build_dir: Path) -> int:
server_sock.bind(str(socket_path)) server_sock.bind(str(socket_path))
server_sock.listen() server_sock.listen()
busybox = shutil.which("busybox")
if busybox is None:
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), "busybox")
init = (temp_path / "init").resolve() init = (temp_path / "init").resolve()
with open(init, "w") as init_file: with open(init, "w") as init_file:
init_file.write( init_file.write(
_INIT_TEMPLATE.format( _INIT_TEMPLATE.format(
_9PFS_MSIZE=_9PFS_MSIZE, _9PFS_MSIZE=_9PFS_MSIZE,
busybox=shlex.quote(busybox),
python=shlex.quote(sys.executable), python=shlex.quote(sys.executable),
cwd=shlex.quote(os.getcwd()), cwd=shlex.quote(os.getcwd()),
command=shlex.quote(command), command=shlex.quote(command),
@ -333,7 +331,7 @@ if __name__ == "__main__":
kernel_dir = next(download_kernels(args.directory, "x86_64", (kernel,))) kernel_dir = next(download_kernels(args.directory, "x86_64", (kernel,)))
try: try:
command = " ".join(args.command) if args.command else '"$BUSYBOX" sh -i' command = " ".join(args.command) if args.command else "sh -i"
sys.exit(run_in_vm(command, kernel_dir, args.directory)) sys.exit(run_in_vm(command, kernel_dir, args.directory))
except LostVMError as e: except LostVMError as e:
print("error:", e, file=sys.stderr) print("error:", e, file=sys.stderr)