Skip to content

Commit e5f9f80

Browse files
committed
boot-qemu.py: Add support for passing through a folder to the guest
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 #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: #81 Signed-off-by: Nathan Chancellor <nathan@kernel.org>
1 parent 8dd8404 commit e5f9f80

2 files changed

Lines changed: 88 additions & 8 deletions

File tree

.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: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# pylint: disable=invalid-name
33

44
import argparse
5+
import contextlib
6+
import grp
57
import os
68
from pathlib import Path
79
import platform
@@ -13,6 +15,7 @@
1315
import utils
1416

1517
base_folder = Path(__file__).resolve().parent
18+
shared_folder = base_folder.joinpath('shared')
1619
supported_architectures = [
1720
"arm", "arm32_v5", "arm32_v6", "arm32_v7", "arm64", "arm64be", "m68k",
1821
"mips", "mipsel", "ppc32", "ppc32_mac", "ppc64", "ppc64le", "riscv",
@@ -83,6 +86,12 @@ def parse_arguments():
8386
help= # noqa: E251
8487
"Number of processors for virtual machine. By default, only machines spawned with KVM will use multiple vCPUS."
8588
)
89+
parser.add_argument(
90+
"--share-folder",
91+
action='store_true',
92+
help= # noqa: E251
93+
f"Share {shared_folder} with the guest using virtiofs (requires interactive, not supported with gdb)."
94+
)
8695
parser.add_argument(
8796
"-t",
8897
"--timeout",
@@ -223,6 +232,7 @@ def setup_cfg(args):
223232
* interactive: Whether or not the user is going to be running the
224233
machine interactively.
225234
* kernel_location: The full path to the kernel image or build folder.
235+
* share_folder_with_guest: Share a folder on the host with a guest.
226236
* smp_requested: Whether or not the user specified a value with
227237
'--smp'.
228238
* smp_value: The value to use with '-smp' (will be used when
@@ -248,6 +258,7 @@ def setup_cfg(args):
248258
"gdb": args.gdb,
249259
"gdb_bin": args.gdb_bin,
250260
"interactive": args.interactive or args.gdb,
261+
"share_folder_with_guest": args.share_folder,
251262
"smp_requested": args.smp is not None,
252263
"smp_value": get_smp_value(args),
253264
"timeout": args.timeout,
@@ -735,8 +746,58 @@ def launch_qemu(cfg):
735746
gdb_bin = cfg["gdb_bin"]
736747
kernel_location = cfg["kernel_location"]
737748
qemu_cmd = cfg["qemu_cmd"]
749+
share_folder_with_guest = cfg["share_folder_with_guest"]
738750
timeout = cfg["timeout"]
739751

752+
if share_folder_with_guest and not interactive:
753+
utils.yellow(
754+
'Shared folder requested without an interactive session, ignoring...'
755+
)
756+
share_folder_with_guest = False
757+
if share_folder_with_guest and gdb:
758+
utils.yellow(
759+
'Shared folder requested during a debugging session, ignoring...')
760+
share_folder_with_guest = False
761+
762+
if share_folder_with_guest:
763+
shared_folder.mkdir(exist_ok=True, parents=True)
764+
765+
# If shared folder was requested, we need to search for virtiofsd in
766+
# certain known locations.
767+
qemu_prefix = Path(qemu_cmd[0]).resolve().parent.parent
768+
virtiofsd_locations = [
769+
Path('libexec', 'virtiofsd'), # Default QEMU installation, Fedora
770+
Path('lib', 'qemu', 'virtiofsd'), # Arch Linux, Debian, Ubuntu
771+
]
772+
virtiofsd = utils.find_first_file(qemu_prefix, virtiofsd_locations)
773+
774+
if not (sudo := shutil.which('sudo')):
775+
raise Exception(
776+
'sudo is required to use virtiofsd but it could not be found!')
777+
utils.green(
778+
'Requesting sudo permission to run virtiofsd in the background...')
779+
subprocess.run([sudo, 'true'], check=True)
780+
781+
virtiofsd_log = base_folder.joinpath('.vfsd.log')
782+
virtiofsd_mem = base_folder.joinpath('.vfsd.mem')
783+
virtiofsd_socket = base_folder.joinpath('.vfsd.sock')
784+
virtiofsd_cmd = [
785+
sudo,
786+
virtiofsd,
787+
f"--socket-group={grp.getgrgid(os.getgid()).gr_name}",
788+
f"--socket-path={virtiofsd_socket}",
789+
'-o', f"source={shared_folder}",
790+
'-o', 'cache=always',
791+
] # yapf: disable
792+
793+
qemu_mem = qemu_cmd[qemu_cmd.index('-m') + 1]
794+
qemu_cmd += [
795+
'-chardev', f"socket,id=char0,path={virtiofsd_socket}",
796+
'-device', 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared',
797+
'-object', f"memory-backend-file,id=shm,mem-path={virtiofsd_mem},share=on,size={qemu_mem}",
798+
'-numa', 'node,memdev=shm',
799+
] # yapf: disable
800+
740801
# Print information about the QEMU binary
741802
pretty_print_qemu_info(qemu_cmd[0])
742803

@@ -782,14 +843,31 @@ def launch_qemu(cfg):
782843
qemu_cmd = timeout_cmd + stdbuf_cmd + qemu_cmd
783844

784845
pretty_print_qemu_cmd(qemu_cmd)
785-
try:
786-
subprocess.run(qemu_cmd, check=True)
787-
except subprocess.CalledProcessError as ex:
788-
if ex.returncode == 124:
789-
utils.red("ERROR: QEMU timed out!")
790-
else:
791-
utils.red("ERROR: QEMU did not exit cleanly!")
792-
sys.exit(ex.returncode)
846+
null_cm = contextlib.nullcontext()
847+
with open(virtiofsd_log, 'w', encoding='utf-8') if share_folder_with_guest else null_cm as vfsd_log, \
848+
subprocess.Popen(virtiofsd_cmd, stderr=vfsd_log, stdout=vfsd_log) if share_folder_with_guest else null_cm as vfsd_process:
849+
try:
850+
subprocess.run(qemu_cmd, check=True)
851+
except subprocess.CalledProcessError as ex:
852+
if ex.returncode == 124:
853+
utils.red("ERROR: QEMU timed out!")
854+
else:
855+
utils.red("ERROR: QEMU did not exit cleanly!")
856+
# If virtiofsd is dead, it is pretty likely that it was the
857+
# cause of QEMU failing so add to the existing exception using
858+
# 'from'.
859+
if vfsd_process and vfsd_process.poll():
860+
vfsd_log_txt = virtiofsd_log.read_text(
861+
encoding='utf-8')
862+
raise Exception(
863+
f"virtiofsd failed with: {vfsd_log_txt}") from ex
864+
sys.exit(ex.returncode)
865+
finally:
866+
if vfsd_process:
867+
vfsd_process.kill()
868+
# Delete the memory to save space, it does not have to be
869+
# persistent
870+
virtiofsd_mem.unlink(missing_ok=True)
793871

794872

795873
if __name__ == '__main__':

0 commit comments

Comments
 (0)