Skip to content

Commit e9b8d5f

Browse files
committed
boot-qemu.py: Add support for mounting a folder into the guest via virtiofs
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 #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: #81 Link: https://gitlab.com/virtio-fs/virtiofsd Signed-off-by: Nathan Chancellor <[email protected]>
1 parent 281a953 commit e9b8d5f

File tree

2 files changed

+121
-9
lines changed

2 files changed

+121
-9
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
qemu-binaries/
22
*.pyc
3+
shared/
4+
.vfsd.*

boot-qemu.py

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from argparse import ArgumentParser
55
import contextlib
6+
import grp
67
import os
78
from pathlib import Path
89
import platform
@@ -15,6 +16,7 @@
1516
import utils
1617

1718
BOOT_UTILS = Path(__file__).resolve().parent
19+
SHARED_FOLDER = Path(BOOT_UTILS, 'shared')
1820
SUPPORTED_ARCHES = [
1921
'arm',
2022
'arm32_v5',
@@ -49,7 +51,10 @@ def __init__(self):
4951
self.kernel = None
5052
self.kernel_config = None
5153
self.kernel_dir = None
54+
self.share_folder_with_guest = False
55+
self.smp = 0
5256
self.supports_efi = False
57+
self.timeout = ''
5358
# It may be tempting to use self.use_kvm during initialization of
5459
# subclasses to set certain properties but the user can explicitly opt
5560
# out of KVM after instantiation, so any decisions based on it should
@@ -72,6 +77,14 @@ def __init__(self):
7277
] # yapf: disable
7378
self._qemu_path = None
7479
self._ram = '512m'
80+
self._vfsd_conf = {
81+
'cmd': [],
82+
'files': {
83+
'log': Path(BOOT_UTILS, '.vfsd.log'),
84+
'mem': Path(BOOT_UTILS, '.vfsd.mem'),
85+
'sock': Path(BOOT_UTILS, '.vfsd.sock'),
86+
},
87+
}
7588

7689
def _find_dtb(self):
7790
if not self._dtb:
@@ -169,6 +182,65 @@ def _get_qemu_ver_tuple(self):
169182
def _have_dev_kvm_access(self):
170183
return os.access('/dev/kvm', os.R_OK | os.W_OK)
171184

185+
def _prepare_for_shared_folder(self):
186+
if self._get_kernel_config_val('CONFIG_VIRTIO_FS') != 'y':
187+
utils.yellow(
188+
'CONFIG_VIRTIO_FS may not be enabled in your configuration, shared folder may not work...'
189+
)
190+
191+
# Print information about using shared folder
192+
utils.green('To mount shared folder in guest (e.g. to /mnt/shared):')
193+
utils.green('\t/ # mkdir /mnt/shared')
194+
utils.green('\t/ # mount -t virtiofs shared /mnt/shared')
195+
196+
SHARED_FOLDER.mkdir(exist_ok=True, parents=True)
197+
198+
# Make sure sudo is available and we have permission to use it
199+
if not (sudo := shutil.which('sudo')):
200+
raise FileNotFoundError(
201+
'sudo is required to use virtiofsd but it could not be found!')
202+
utils.green(
203+
'Requesting sudo permission to run virtiofsd in the background...')
204+
subprocess.run([sudo, 'true'], check=True)
205+
206+
# There are two implementations of virtiofsd. The original C
207+
# implementation was bundled and built with QEMU up until 8.0, where it
208+
# was removed after being deprecated in 7.0:
209+
#
210+
# https://lore.kernel.org/[email protected]/
211+
#
212+
# The standalone Rust implementation is preferred now, which should be
213+
# available in PATH. If it is not available, see if there is a C
214+
# implementation available in QEMU's prefix.
215+
if not (virtiofsd := shutil.which('virtiofsd')):
216+
utils.yellow(
217+
'Could not find Rust implementation of virtiofsd (https://gitlab.com/virtio-fs/virtiofsd), searching for old C implementation...'
218+
)
219+
220+
qemu_prefix = self._qemu_path.resolve().parents[1]
221+
virtiofsd_locations = [
222+
Path('libexec/virtiofsd'), # Default QEMU installation, Fedora
223+
Path('lib/qemu/virtiofsd'), # Arch Linux, Debian, Ubuntu
224+
]
225+
virtiofsd = utils.find_first_file(qemu_prefix, virtiofsd_locations)
226+
227+
# Prepare QEMU arguments
228+
self._qemu_args += [
229+
'-chardev', f"socket,id=char0,path={self._vfsd_conf['files']['sock']}",
230+
'-device', 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared',
231+
'-object', f"memory-backend-file,id=shm,mem-path={self._vfsd_conf['files']['mem']},share=on,size={self._ram}",
232+
'-numa', 'node,memdev=shm',
233+
] # yapf: disable
234+
235+
self._vfsd_conf['cmd'] = [
236+
sudo,
237+
virtiofsd,
238+
f"--socket-group={grp.getgrgid(os.getgid()).gr_name}",
239+
f"--socket-path={self._vfsd_conf['files']['sock']}",
240+
'-o', f"source={SHARED_FOLDER}",
241+
'-o', 'cache=always',
242+
] # yapf: disable
243+
172244
def _prepare_initrd(self):
173245
if not self._initrd_arch:
174246
raise RuntimeError('No initrd architecture specified?')
@@ -184,6 +256,9 @@ def _prepare_initrd(self):
184256
return dst
185257

186258
def _run_fg(self):
259+
if self.share_folder_with_guest:
260+
self._prepare_for_shared_folder()
261+
187262
# Pretty print and run QEMU command
188263
qemu_cmd = []
189264

@@ -196,15 +271,32 @@ def _run_fg(self):
196271

197272
qemu_cmd += [self._qemu_path, *self._qemu_args]
198273

199-
print(f"$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}")
200-
try:
201-
subprocess.run(qemu_cmd, check=True)
202-
except subprocess.CalledProcessError as err:
203-
if err.returncode == 124:
204-
utils.red("ERROR: QEMU timed out!")
205-
else:
206-
utils.red("ERROR: QEMU did not exit cleanly!")
207-
sys.exit(err.returncode)
274+
print(f"\n$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}")
275+
null_cm = contextlib.nullcontext()
276+
with self._vfsd_conf['files']['log'].open('w', encoding='utf-8') if self.share_folder_with_guest else null_cm as vfsd_log, \
277+
subprocess.Popen(self._vfsd_conf['cmd'], stderr=vfsd_log, stdout=vfsd_log) if self.share_folder_with_guest else null_cm as vfsd_proc:
278+
try:
279+
subprocess.run(qemu_cmd, check=True)
280+
except subprocess.CalledProcessError as err:
281+
if err.returncode == 124:
282+
utils.red("ERROR: QEMU timed out!")
283+
else:
284+
utils.red("ERROR: QEMU did not exit cleanly!")
285+
# If virtiofsd is dead, it is pretty likely that it was the
286+
# cause of QEMU failing so add to the existing exception using
287+
# 'from'.
288+
if vfsd_proc and vfsd_proc.poll():
289+
# yapf: disable
290+
vfsd_log_txt = self._vfsd_conf['files']['log'].read_text(encoding='utf-8')
291+
raise RuntimeError(f"virtiofsd failed with: {vfsd_log_txt}") from err
292+
# yapf: enable
293+
sys.exit(err.returncode)
294+
finally:
295+
if vfsd_proc:
296+
vfsd_proc.kill()
297+
# Delete the memory to save space, it does not have to be
298+
# persistent
299+
self._vfsd_conf['files']['mem'].unlink(missing_ok=True)
208300

209301
def _run_gdb(self):
210302
qemu_cmd = [self._qemu_path, *self._qemu_args]
@@ -711,6 +803,12 @@ def parse_arguments():
711803
help=
712804
'Number of processors for virtual machine (default: only KVM machines will use multiple vCPUs.)',
713805
)
806+
parser.add_argument(
807+
'--share-folder-with-guest',
808+
action='store_true',
809+
help=
810+
f"Share {SHARED_FOLDER} with the guest using virtiofs (requires interactive, not supported with gdb).",
811+
)
714812
parser.add_argument('-t',
715813
'--timeout',
716814
default='3m',
@@ -772,6 +870,18 @@ def parse_arguments():
772870
if args.no_kvm:
773871
runner.use_kvm = False
774872

873+
if args.share_folder_with_guest:
874+
if args.gdb:
875+
utils.yellow(
876+
'Shared folder requested during a debugging session, ignoring...'
877+
)
878+
elif not args.interactive:
879+
utils.yellow(
880+
'Shared folder requested without an interactive session, ignoring...'
881+
)
882+
else:
883+
runner.share_folder_with_guest = True
884+
775885
if args.smp:
776886
runner.smp = args.smp
777887

0 commit comments

Comments
 (0)