Skip to content

Commit 715bfba

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 e8b85f0 commit 715bfba

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,3 +1,5 @@
11
images/
22
qemu-binaries/
33
*.pyc
4+
shared/
5+
.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
@@ -14,6 +15,7 @@
1415

1516
import utils
1617

18+
SHARED_FOLDER = Path(utils.BOOT_UTILS, 'shared')
1719
SUPPORTED_ARCHES = [
1820
'arm',
1921
'arm32_v5',
@@ -50,7 +52,10 @@ def __init__(self):
5052
self.kernel = None
5153
self.kernel_config = None
5254
self.kernel_dir = None
55+
self.share_folder_with_guest = False
56+
self.smp = 0
5357
self.supports_efi = False
58+
self.timeout = ''
5459
# It may be tempting to use self.use_kvm during initialization of
5560
# subclasses to set certain properties but the user can explicitly opt
5661
# 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(utils.BOOT_UTILS, '.vfsd.log'),
84+
'mem': Path(utils.BOOT_UTILS, '.vfsd.mem'),
85+
'sock': Path(utils.BOOT_UTILS, '.vfsd.sock'),
86+
},
87+
}
7588

7689
def _find_dtb(self):
7790
if not self._dtbs:
@@ -170,13 +183,75 @@ def _get_qemu_ver_tuple(self):
170183
def _have_dev_kvm_access(self):
171184
return os.access('/dev/kvm', os.R_OK | os.W_OK)
172185

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

179251
def _run_fg(self):
252+
if self.share_folder_with_guest:
253+
self._prepare_for_shared_folder()
254+
180255
# Pretty print and run QEMU command
181256
qemu_cmd = []
182257

@@ -189,15 +264,32 @@ def _run_fg(self):
189264

190265
qemu_cmd += [self._qemu_path, *self._qemu_args]
191266

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

202294
def _run_gdb(self):
203295
qemu_cmd = [self._qemu_path, *self._qemu_args]
@@ -817,6 +909,12 @@ def parse_arguments():
817909
help=
818910
'Number of processors for virtual machine (default: only KVM machines will use multiple vCPUs.)',
819911
)
912+
parser.add_argument(
913+
'--share-folder-with-guest',
914+
action='store_true',
915+
help=
916+
f"Share {SHARED_FOLDER} with the guest using virtiofs (requires interactive, not supported with gdb).",
917+
)
820918
parser.add_argument('-t',
821919
'--timeout',
822920
default='3m',
@@ -886,6 +984,18 @@ def parse_arguments():
886984
if args.no_kvm:
887985
runner.use_kvm = False
888986

987+
if args.share_folder_with_guest:
988+
if args.gdb:
989+
utils.yellow(
990+
'Shared folder requested during a debugging session, ignoring...'
991+
)
992+
elif not args.interactive:
993+
utils.yellow(
994+
'Shared folder requested without an interactive session, ignoring...'
995+
)
996+
else:
997+
runner.share_folder_with_guest = True
998+
889999
if args.smp:
8901000
runner.smp = args.smp
8911001

0 commit comments

Comments
 (0)