Skip to content

Enable VirtIO block to access hostOS /dev/ block devices #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .ci/boot-linux-prepare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash

. .ci/common.sh

check_platform

VBLK_IMG=build/disk.img
which dd >/dev/null 2>&1 || { echo "Error: dd not found"; exit 1; }
which mkfs.ext4 >/dev/null 2>&1 || which $(brew --prefix e2fsprogs)/sbin/mkfs.ext4 >/dev/null 2>&1 || \
{ echo "Error: mkfs.ext4 not found"; exit 1; }
which 7z >/dev/null 2>&1 || { echo "Error: 7z not found"; exit 1; }

ACTION=$1

case "$ACTION" in
setup)
# Setup a disk image
dd if=/dev/zero of=${VBLK_IMG} bs=4M count=32

# Setup a /dev/ block device with ${VBLK_IMG} to test guestOS access to hostOS /dev/ block device
case "${OS_TYPE}" in
Linux)
mkfs.ext4 ${VBLK_IMG}
BLK_DEV=$(losetup -f)
losetup ${BLK_DEV} ${VBLK_IMG}
;;
Darwin)
$(brew --prefix e2fsprogs)/sbin/mkfs.ext4 ${VBLK_IMG}
BLK_DEV=$(hdiutil attach -nomount ${VBLK_IMG})
;;
esac

# On Linux, ${VBLK_IMG} will be created by root and owned by root:root.
# Even if "others" have read and write (rw) permissions, accessing the file for certain operations may
# still require elevated privileges (e.g., setuid).
# To simplify this, we change the ownership to a non-root user.
# Use this with caution—changing ownership to runner:runner is specific to the GitHub CI environment.
chown runner: ${VBLK_IMG}
# Add other's rw permission to the disk image and device, so non-superuser can rw them
chmod o+r,o+w ${VBLK_IMG}
chmod o+r,o+w ${BLK_DEV}

# Export ${BLK_DEV} to a tmp file. Then, source to "$GITHUB_ENV" in job step.
echo "export BLK_DEV=${BLK_DEV}" > "${TMP_FILE}"
;;
cleanup)
# Detach the /dev/loopx(Linux) or /dev/diskx(Darwin)
case "${OS_TYPE}" in
Linux)
losetup -d ${BLK_DEV}
;;
Darwin)
hdiutil detach ${BLK_DEV}
;;
esac

# delete disk image
rm -f ${VBLK_IMG}

# delete tmp file
rm "${TMP_FILE}"
;;
*)
printf "Usage: %s {setup|cleanup}\n" "$0"
exit 1
;;
esac
111 changes: 71 additions & 40 deletions .ci/boot-linux.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#!/usr/bin/env bash

. .ci/common.sh

check_platform

function cleanup {
sleep 1
pkill -9 rv32emu
Expand All @@ -8,71 +12,98 @@ function cleanup {
function ASSERT {
$*
local RES=$?
if [ $RES -ne 0 ]; then
if [ ${RES} -ne 0 ]; then
echo 'Assert failed: "' $* '"'
exit $RES
exit ${RES}
fi
}

cleanup

ENABLE_VBLK=1
VBLK_IMG=build/disk.img
which dd >/dev/null 2>&1 || ENABLE_VBLK=0
which mkfs.ext4 >/dev/null 2>&1 || which $(brew --prefix e2fsprogs)/sbin/mkfs.ext4 >/dev/null 2>&1 || ENABLE_VBLK=0
which 7z >/dev/null 2>&1 || ENABLE_VBLK=0
[ -f "${VBLK_IMG}" ] || ENABLE_VBLK=0

TIMEOUT=50
OPTS=" -k build/linux-image/Image "
OPTS+=" -i build/linux-image/rootfs.cpio "
if [ "$ENABLE_VBLK" -eq "1" ]; then
dd if=/dev/zero of=$VBLK_IMG bs=4M count=32
mkfs.ext4 $VBLK_IMG || $(brew --prefix e2fsprogs)/sbin/mkfs.ext4 $VBLK_IMG
OPTS+=" -x vblk:$VBLK_IMG "
else
printf "Virtio-blk Test...Passed\n"
fi
RUN_LINUX="build/rv32emu ${OPTS}"
OPTS_BASE=" -k build/linux-image/Image"
OPTS_BASE+=" -i build/linux-image/rootfs.cpio"

if [ "$ENABLE_VBLK" -eq "1" ]; then
ASSERT expect <<DONE
set timeout ${TIMEOUT}
spawn ${RUN_LINUX}
expect "buildroot login:" { send "root\n" } timeout { exit 1 }
expect "# " { send "uname -a\n" } timeout { exit 2 }
expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 }
expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 }
expect "# " { send "sync\n" } timeout { exit 3 }
expect "# " { send "umount mnt\n" } timeout { exit 3 }
expect "# " { send "\x01"; send "x" } timeout { exit 3 }
DONE
else
ASSERT expect <<DONE
set timeout ${TIMEOUT}
spawn ${RUN_LINUX}
TEST_OPTIONS=("base (${OPTS_BASE})")
EXPECT_CMDS=('
expect "buildroot login:" { send "root\n" } timeout { exit 1 }
expect "# " { send "uname -a\n" } timeout { exit 2 }
expect "riscv32 GNU/Linux" { send "\x01"; send "x" } timeout { exit 3 }
DONE
fi
ret=$?
cleanup
')

COLOR_G='\e[32;01m' # Green
COLOR_R='\e[31;01m' # Red
COLOR_Y='\e[33;01m' # Yellow
COLOR_N='\e[0m' # No color

MESSAGES=("${COLOR_G}OK!" \
"${COLOR_R}Fail to boot" \
"${COLOR_R}Fail to login" \
"${COLOR_R}Fail to run commands" \
"${COLOR_R}Fail to find emu.txt in $VBLK_IMG"\
"${COLOR_R}Fail to find emu.txt in ${VBLK_IMG}"\
)

printf "\nBoot Linux Test: [ ${MESSAGES[$ret]}${COLOR_N} ]\n"
if [ "$ENABLE_VBLK" -eq "1" ]; then
7z l $VBLK_IMG | grep emu.txt >/dev/null 2>&1 || ret=4
printf "Virtio-blk Test: [ ${MESSAGES[$ret]}${COLOR_N} ]\n"
if [ "${ENABLE_VBLK}" -eq "1" ]; then
# Read-only
TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG},readonly")
EXPECT_CMDS+=('
expect "buildroot login:" { send "root\n" } timeout { exit 1 }
expect "# " { send "uname -a\n" } timeout { exit 2 }
expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 }
expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 }
expect -ex "-sh: can'\''t create mnt/emu.txt: Read-only file system" {} timeout { exit 3 }
expect "# " { send "\x01"; send "x" } timeout { exit 3 }
')

# Read-write using disk image
TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG}")
VBLK_EXPECT_CMDS='
expect "buildroot login:" { send "root\n" } timeout { exit 1 }
expect "# " { send "uname -a\n" } timeout { exit 2 }
expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 }
expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 }
expect "# " { send "sync\n" } timeout { exit 3 }
expect "# " { send "umount mnt\n" } timeout { exit 3 }
expect "# " { send "\x01"; send "x" } timeout { exit 3 }
'
EXPECT_CMDS+=("${VBLK_EXPECT_CMDS}")

# Read-write using /dev/loopx(Linux) or /dev/diskx(Darwin) block device
TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${BLK_DEV}")
EXPECT_CMDS+=("${VBLK_EXPECT_CMDS}")
fi

for i in "${!TEST_OPTIONS[@]}"; do
printf "${COLOR_Y}===== Test option: ${TEST_OPTIONS[$i]} =====${COLOR_N}\n"

OPTS="${OPTS_BASE}"
# No need to add option when running base test
if [[ ! "${TEST_OPTIONS[$i]}" =~ "base" ]]; then
OPTS+="${TEST_OPTIONS[$i]}"
fi
RUN_LINUX="build/rv32emu ${OPTS}"

ASSERT expect <<-DONE
set timeout ${TIMEOUT}
spawn ${RUN_LINUX}
${EXPECT_CMDS[$i]}
DONE

ret=$?
cleanup

printf "\nBoot Linux Test: [ ${MESSAGES[$ret]}${COLOR_N} ]\n"
if [[ "${TEST_OPTIONS[$i]}" =~ vblk ]]; then
# read-only test first, so the emu.txt definitely does not exist, skipping the check
if [[ ! "${TEST_OPTIONS[$i]}" =~ readonly ]]; then
7z l ${VBLK_IMG} | grep emu.txt >/dev/null 2>&1 || ret=4
fi
printf "Virtio-blk Test: [ ${MESSAGES[$ret]}${COLOR_N} ]\n"
fi
done

exit ${ret}
26 changes: 20 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,14 @@ jobs:
actions-cache-folder: 'emsdk-cache'
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(nproc)" >> $GITHUB_ENV
echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV=\${BLK_DEV} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
- name: fetch artifact first to reduce HTTP requests
env:
CC: ${{ steps.install_cc.outputs.cc }}
Expand Down Expand Up @@ -265,15 +272,15 @@ jobs:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 $PARALLEL && make ENABLE_SYSTEM=1 artifact $PARALLEL
.ci/boot-linux.sh
bash -c "${BOOT_LINUX_TEST}"
make ENABLE_SYSTEM=1 clean
if: ${{ always() }}
- name: boot Linux kernel test (JIT)
env:
CC: ${{ steps.install_cc.outputs.cc }}
run: |
make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_T2C=0 ENABLE_MOP_FUSION=0 $PARALLEL && make ENABLE_SYSTEM=1 artifact $PARALLEL
.ci/boot-linux.sh
bash -c "${BOOT_LINUX_TEST}"
make ENABLE_SYSTEM=1 ENABLE_JIT=1 ENABLE_T2C=0 ENABLE_MOP_FUSION=0 clean
if: ${{ always() }}
- name: Architecture test
Expand All @@ -292,7 +299,7 @@ jobs:
uses: actions/checkout@v4
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(nproc)" >> $GITHUB_ENV
echo "PARALLEL=-j$(nproc)" >> "$GITHUB_ENV"
- name: build artifact
# The GitHub Action for non-x86 CPU
uses: allinurl/run-on-arch-action@master
Expand Down Expand Up @@ -351,7 +358,14 @@ jobs:
actions-cache-folder: 'emsdk-cache'
- name: Set parallel jobs variable
run: |
echo "PARALLEL=-j$(sysctl -n hw.logicalcpu)" >> $GITHUB_ENV
echo "PARALLEL=-j$(sysctl -n hw.logicalcpu)" >> "$GITHUB_ENV"
echo "BOOT_LINUX_TEST=TMP_FILE=\$(mktemp "$RUNNER_TEMP/tmpfile.XXXXXX"); \
sudo env TMP_FILE=\${TMP_FILE} .ci/boot-linux-prepare.sh setup; \
. \${TMP_FILE}; \
.ci/boot-linux.sh; \
EXIT_CODE=\$?; \
sudo env TMP_FILE=\${TMP_FILE} BLK_DEV=\${BLK_DEV} .ci/boot-linux-prepare.sh cleanup; \
exit \${EXIT_CODE};" >> "$GITHUB_ENV"
- name: Symlink gcc-14 due to the default /usr/local/bin/gcc links to system's clang
run: |
ln -s /opt/homebrew/opt/gcc/bin/gcc-14 /usr/local/bin/gcc-14
Expand Down Expand Up @@ -463,7 +477,7 @@ jobs:
run: |
make distclean && make INITRD_SIZE=32 ENABLE_SYSTEM=1 $PARALLEL && \
make ENABLE_SYSTEM=1 artifact $PARALLEL
.ci/boot-linux.sh
bash -c "${BOOT_LINUX_TEST}"
make ENABLE_SYSTEM=1 clean
if: ${{ always() }}
- name: Architecture test
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Generate ext4 image file for virtio block device in Unix-like system:
$ dd if=/dev/zero of=disk.img bs=4M count=32
$ mkfs.ext4 disk.img
```
Instead of creating a new block device image, you can share the hostOS's existing block devices. For example, on macOS host, specify the block device path as `-x vblk:/dev/disk3`, or on Linux host as `-x vblk:/dev/loop3`, assuming these paths point to valid block devices.

Mount the virtual block device and create a test file after booting, note that root privilege is required to mount and unmount a disk:
```shell
# mkdir mnt
Expand Down
Loading