Skip to content

Commit

Permalink
boot-qemu.py: Add support for mounting a folder into the guest via vi…
Browse files Browse the repository at this point in the history
…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 run virtiofsd, which is included with
most base QEMU packages. 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

Link: ClangBuiltLinux#81
Signed-off-by: Nathan Chancellor <[email protected]>
  • Loading branch information
nathanchance committed Feb 22, 2023
1 parent 8946cc4 commit 045d64a
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
qemu-binaries/
*.pyc
shared/
.vfsd.*
111 changes: 103 additions & 8 deletions boot-qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from argparse import ArgumentParser
import contextlib
import grp
import os
from pathlib import Path
import platform
Expand All @@ -15,6 +16,7 @@
import utils

BOOT_UTILS = Path(__file__).resolve().parent
SHARED_FOLDER = Path(BOOT_UTILS, 'shared')
SUPPORTED_ARCHES = [
'arm',
'arm32_v5',
Expand Down Expand Up @@ -49,6 +51,7 @@ def __init__(self):
self.kernel = None
self.kernel_config = None
self.kernel_dir = None
self.share_folder_with_guest = False
self.smp = 0
self.timeout = ''
# It may be tempting to use self.use_kvm during initialization of
Expand All @@ -73,6 +76,14 @@ def __init__(self):
] # yapf: disable
self._qemu_path = None
self._ram = '512m'
self._vfsd_conf = {
'cmd': [],
'files': {
'log': Path(BOOT_UTILS, '.vfsd.log'),
'mem': Path(BOOT_UTILS, '.vfsd.mem'),
'sock': Path(BOOT_UTILS, '.vfsd.sock'),
},
}

def _can_use_kvm(self):
return False
Expand Down Expand Up @@ -187,6 +198,52 @@ def _prepare_initrd(self):

return dst

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)

# Find virtiofsd relative to QEMU's prefix
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 _run_gdb(self):
qemu_cmd = [self._qemu_path, *self._qemu_args]

Expand Down Expand Up @@ -223,6 +280,9 @@ def _run_gdb(self):
break

def _run_fg(self):
if self.share_folder_with_guest:
self._prepare_for_shared_folder()

# Pretty print and run QEMU command
qemu_cmd = []

Expand All @@ -236,14 +296,31 @@ def _run_fg(self):
qemu_cmd += [self._qemu_path, *self._qemu_args]

print(f"\n$ {' '.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)
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 _set_kernel_vars(self):
if self.kernel:
Expand Down Expand Up @@ -719,6 +796,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',
Expand Down Expand Up @@ -780,6 +863,18 @@ def parse_arguments():
if args.no_kvm:
runner.use_kvm = False

if args.share_folder_with_guest:
if not args.interactive:
utils.yellow(
'Shared folder requested without an interactive session, ignoring...',
)
elif args.gdb:
utils.yellow(
'Shared folder requested during a debugging session, ignoring...',
)
else:
runner.share_folder_with_guest = True

if args.smp:
runner.smp = args.smp

Expand Down

0 comments on commit 045d64a

Please sign in to comment.