forked from ClangBuiltLinux/boot-utils
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
boot-qemu.py: Add support for mounting a folder into the guest via vi…
…rtiofs virtiofs, available in QEMU 5.2 or newer and Linux guests 5.4 or newer, is a more modern way to pass local folders along to QEMU, as it takes advantage of the fact that the folders are on the same machine as the hypervisor. To use virtiofs, we first need to find and run virtiofsd, which has two different implementations: a C implementation included with QEMU up until 8.0 (available on most distros) and a standalone Rust implementation available on GitLab (not packaged on many distros but easy to build and install). Once we find it, we run it in the background and connect to it using some QEMU parameters, which were shamelessly taken from the official virtiofs website: https://virtio-fs.gitlab.io/howto-qemu.html To use it within the guest (you can use a different path than /mnt/shared but 'mount -t virtio shared' must be used): # mkdir /mnt/shared # mount -t virtiofs shared /mnt/shared # echo "$(uname -a)" >/mnt/shared/foo On the host: $ cat shared/foo Linux (none) 6.1.0-rc8-next-20221207 ClangBuiltLinux#2 SMP PREEMPT Wed Dec 7 14:56:03 MST 2022 aarch64 GNU/Linux This does require guest kernel support (CONFIG_VIRTIO_FS=y), otherwise it will not work inside the guest: / # mount -t virtiofs shared /mnt/shared mount: mounting shared on /mnt/shared failed: No such device Closes: ClangBuiltLinux#81 Link: https://gitlab.com/virtio-fs/virtiofsd Signed-off-by: Nathan Chancellor <[email protected]>
- Loading branch information
1 parent
e8b85f0
commit 715bfba
Showing
2 changed files
with
121 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
images/ | ||
qemu-binaries/ | ||
*.pyc | ||
shared/ | ||
.vfsd.* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
from argparse import ArgumentParser | ||
import contextlib | ||
import grp | ||
import os | ||
from pathlib import Path | ||
import platform | ||
|
@@ -14,6 +15,7 @@ | |
|
||
import utils | ||
|
||
SHARED_FOLDER = Path(utils.BOOT_UTILS, 'shared') | ||
SUPPORTED_ARCHES = [ | ||
'arm', | ||
'arm32_v5', | ||
|
@@ -50,7 +52,10 @@ def __init__(self): | |
self.kernel = None | ||
self.kernel_config = None | ||
self.kernel_dir = None | ||
self.share_folder_with_guest = False | ||
self.smp = 0 | ||
self.supports_efi = False | ||
self.timeout = '' | ||
# It may be tempting to use self.use_kvm during initialization of | ||
# subclasses to set certain properties but the user can explicitly opt | ||
# out of KVM after instantiation, so any decisions based on it should | ||
|
@@ -72,6 +77,14 @@ def __init__(self): | |
] # yapf: disable | ||
self._qemu_path = None | ||
self._ram = '512m' | ||
self._vfsd_conf = { | ||
'cmd': [], | ||
'files': { | ||
'log': Path(utils.BOOT_UTILS, '.vfsd.log'), | ||
'mem': Path(utils.BOOT_UTILS, '.vfsd.mem'), | ||
'sock': Path(utils.BOOT_UTILS, '.vfsd.sock'), | ||
}, | ||
} | ||
|
||
def _find_dtb(self): | ||
if not self._dtbs: | ||
|
@@ -170,13 +183,75 @@ def _get_qemu_ver_tuple(self): | |
def _have_dev_kvm_access(self): | ||
return os.access('/dev/kvm', os.R_OK | os.W_OK) | ||
|
||
def _prepare_for_shared_folder(self): | ||
if self._get_kernel_config_val('CONFIG_VIRTIO_FS') != 'y': | ||
utils.yellow( | ||
'CONFIG_VIRTIO_FS may not be enabled in your configuration, shared folder may not work...' | ||
) | ||
|
||
# Print information about using shared folder | ||
utils.green('To mount shared folder in guest (e.g. to /mnt/shared):') | ||
utils.green('\t/ # mkdir /mnt/shared') | ||
utils.green('\t/ # mount -t virtiofs shared /mnt/shared') | ||
|
||
SHARED_FOLDER.mkdir(exist_ok=True, parents=True) | ||
|
||
# Make sure sudo is available and we have permission to use it | ||
if not (sudo := shutil.which('sudo')): | ||
raise FileNotFoundError( | ||
'sudo is required to use virtiofsd but it could not be found!') | ||
utils.green( | ||
'Requesting sudo permission to run virtiofsd in the background...') | ||
subprocess.run([sudo, 'true'], check=True) | ||
|
||
# There are two implementations of virtiofsd. The original C | ||
# implementation was bundled and built with QEMU up until 8.0, where it | ||
# was removed after being deprecated in 7.0: | ||
# | ||
# https://lore.kernel.org/[email protected]/ | ||
# | ||
# The standalone Rust implementation is preferred now, which should be | ||
# available in PATH. If it is not available, see if there is a C | ||
# implementation available in QEMU's prefix. | ||
if not (virtiofsd := shutil.which('virtiofsd')): | ||
utils.yellow( | ||
'Could not find Rust implementation of virtiofsd (https://gitlab.com/virtio-fs/virtiofsd), searching for old C implementation...' | ||
) | ||
|
||
qemu_prefix = self._qemu_path.resolve().parents[1] | ||
virtiofsd_locations = [ | ||
Path('libexec/virtiofsd'), # Default QEMU installation, Fedora | ||
Path('lib/qemu/virtiofsd'), # Arch Linux, Debian, Ubuntu | ||
] | ||
virtiofsd = utils.find_first_file(qemu_prefix, virtiofsd_locations) | ||
|
||
# Prepare QEMU arguments | ||
self._qemu_args += [ | ||
'-chardev', f"socket,id=char0,path={self._vfsd_conf['files']['sock']}", | ||
'-device', 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared', | ||
'-object', f"memory-backend-file,id=shm,mem-path={self._vfsd_conf['files']['mem']},share=on,size={self._ram}", | ||
'-numa', 'node,memdev=shm', | ||
] # yapf: disable | ||
|
||
self._vfsd_conf['cmd'] = [ | ||
sudo, | ||
virtiofsd, | ||
f"--socket-group={grp.getgrgid(os.getgid()).gr_name}", | ||
f"--socket-path={self._vfsd_conf['files']['sock']}", | ||
'-o', f"source={SHARED_FOLDER}", | ||
'-o', 'cache=always', | ||
] # yapf: disable | ||
|
||
def _prepare_initrd(self): | ||
if not self._initrd_arch: | ||
raise RuntimeError('No initrd architecture specified?') | ||
return utils.prepare_initrd(self._initrd_arch, | ||
gh_json_file=self.gh_json_file) | ||
|
||
def _run_fg(self): | ||
if self.share_folder_with_guest: | ||
self._prepare_for_shared_folder() | ||
|
||
# Pretty print and run QEMU command | ||
qemu_cmd = [] | ||
|
||
|
@@ -189,15 +264,32 @@ def _run_fg(self): | |
|
||
qemu_cmd += [self._qemu_path, *self._qemu_args] | ||
|
||
print(f"$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}") | ||
try: | ||
subprocess.run(qemu_cmd, check=True) | ||
except subprocess.CalledProcessError as err: | ||
if err.returncode == 124: | ||
utils.red("ERROR: QEMU timed out!") | ||
else: | ||
utils.red("ERROR: QEMU did not exit cleanly!") | ||
sys.exit(err.returncode) | ||
print(f"\n$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}") | ||
null_cm = contextlib.nullcontext() | ||
with self._vfsd_conf['files']['log'].open('w', encoding='utf-8') if self.share_folder_with_guest else null_cm as vfsd_log, \ | ||
subprocess.Popen(self._vfsd_conf['cmd'], stderr=vfsd_log, stdout=vfsd_log) if self.share_folder_with_guest else null_cm as vfsd_proc: | ||
try: | ||
subprocess.run(qemu_cmd, check=True) | ||
except subprocess.CalledProcessError as err: | ||
if err.returncode == 124: | ||
utils.red("ERROR: QEMU timed out!") | ||
else: | ||
utils.red("ERROR: QEMU did not exit cleanly!") | ||
# If virtiofsd is dead, it is pretty likely that it was the | ||
# cause of QEMU failing so add to the existing exception using | ||
# 'from'. | ||
if vfsd_proc and vfsd_proc.poll(): | ||
# yapf: disable | ||
vfsd_log_txt = self._vfsd_conf['files']['log'].read_text(encoding='utf-8') | ||
raise RuntimeError(f"virtiofsd failed with: {vfsd_log_txt}") from err | ||
# yapf: enable | ||
sys.exit(err.returncode) | ||
finally: | ||
if vfsd_proc: | ||
vfsd_proc.kill() | ||
# Delete the memory to save space, it does not have to be | ||
# persistent | ||
self._vfsd_conf['files']['mem'].unlink(missing_ok=True) | ||
|
||
def _run_gdb(self): | ||
qemu_cmd = [self._qemu_path, *self._qemu_args] | ||
|
@@ -817,6 +909,12 @@ def parse_arguments(): | |
help= | ||
'Number of processors for virtual machine (default: only KVM machines will use multiple vCPUs.)', | ||
) | ||
parser.add_argument( | ||
'--share-folder-with-guest', | ||
action='store_true', | ||
help= | ||
f"Share {SHARED_FOLDER} with the guest using virtiofs (requires interactive, not supported with gdb).", | ||
) | ||
parser.add_argument('-t', | ||
'--timeout', | ||
default='3m', | ||
|
@@ -886,6 +984,18 @@ def parse_arguments(): | |
if args.no_kvm: | ||
runner.use_kvm = False | ||
|
||
if args.share_folder_with_guest: | ||
if args.gdb: | ||
utils.yellow( | ||
'Shared folder requested during a debugging session, ignoring...' | ||
) | ||
elif not args.interactive: | ||
utils.yellow( | ||
'Shared folder requested without an interactive session, ignoring...' | ||
) | ||
else: | ||
runner.share_folder_with_guest = True | ||
|
||
if args.smp: | ||
runner.smp = args.smp | ||
|
||
|