33
44from argparse import ArgumentParser
55import contextlib
6+ import grp
67import os
78from pathlib import Path
89import platform
1415
1516import utils
1617
18+ SHARED_FOLDER = Path (utils .BOOT_UTILS , 'shared' )
1719SUPPORTED_ARCHES = [
1820 'arm' ,
1921 'arm32_v5' ,
@@ -40,7 +42,7 @@ class QEMURunner:
4042
4143 def __init__ (self ):
4244
43- # Properties that can be adjusted by the user or class
45+ # Properties that can be adjusted by the user or class (keep alphabetized if possible)
4446 self .cmdline = []
4547 self .efi = False
4648 self .gdb = False
@@ -51,14 +53,15 @@ def __init__(self):
5153 self .kernel_config = None
5254 self .kernel_dir = None
5355 self .memory = '512m'
56+ self .share_folder_with_guest = False
57+ self .smp = 0
5458 self .supports_efi = False
59+ self .timeout = ''
5560 # It may be tempting to use self.use_kvm during initialization of
5661 # subclasses to set certain properties but the user can explicitly opt
5762 # out of KVM after instantiation, so any decisions based on it should
5863 # be confined to run().
5964 self .use_kvm = False
60- self .smp = 0
61- self .timeout = ''
6265
6366 self ._default_kernel_path = None
6467 self ._dtbs = []
@@ -72,6 +75,14 @@ def __init__(self):
7275 '-nodefaults' ,
7376 ] # yapf: disable
7477 self ._qemu_path = None
78+ self ._vfsd_conf = {
79+ 'cmd' : [],
80+ 'files' : {
81+ 'log' : Path (utils .BOOT_UTILS , '.vfsd.log' ),
82+ 'mem' : Path (utils .BOOT_UTILS , '.vfsd.mem' ),
83+ 'sock' : Path (utils .BOOT_UTILS , '.vfsd.sock' ),
84+ },
85+ }
7586
7687 def _find_dtb (self ):
7788 if not self ._dtbs :
@@ -173,13 +184,75 @@ def _get_qemu_ver_tuple(self):
173184 def _have_dev_kvm_access (self ):
174185 return os .access ('/dev/kvm' , os .R_OK | os .W_OK )
175186
187+ def _prepare_for_shared_folder (self ):
188+ if self ._get_kernel_config_val ('CONFIG_VIRTIO_FS' ) != 'y' :
189+ utils .yellow (
190+ 'CONFIG_VIRTIO_FS may not be enabled in your configuration, shared folder may not work...'
191+ )
192+
193+ # Print information about using shared folder
194+ utils .green ('To mount shared folder in guest (e.g. to /mnt/shared):' )
195+ utils .green ('\t / # mkdir /mnt/shared' )
196+ utils .green ('\t / # mount -t virtiofs shared /mnt/shared' )
197+
198+ SHARED_FOLDER .mkdir (exist_ok = True , parents = True )
199+
200+ # Make sure sudo is available and we have permission to use it
201+ if not (sudo := shutil .which ('sudo' )):
202+ raise FileNotFoundError (
203+ 'sudo is required to use virtiofsd but it could not be found!' )
204+ utils .green (
205+ 'Requesting sudo permission to run virtiofsd in the background...' )
206+ subprocess .run ([sudo , 'true' ], check = True )
207+
208+ # There are two implementations of virtiofsd. The original C
209+ # implementation was bundled and built with QEMU up until 8.0, where it
210+ # was removed after being deprecated in 7.0:
211+ #
212+ # https://lore.kernel.org/20230216182628.126139-1-dgilbert@redhat.com/
213+ #
214+ # The standalone Rust implementation is preferred now, which should be
215+ # available in PATH. If it is not available, see if there is a C
216+ # implementation available in QEMU's prefix.
217+ if not (virtiofsd := shutil .which ('virtiofsd' )):
218+ utils .yellow (
219+ 'Could not find Rust implementation of virtiofsd (https://gitlab.com/virtio-fs/virtiofsd), searching for old C implementation...'
220+ )
221+
222+ qemu_prefix = self ._qemu_path .resolve ().parents [1 ]
223+ virtiofsd_locations = [
224+ Path ('libexec/virtiofsd' ), # Default QEMU installation, Fedora
225+ Path ('lib/qemu/virtiofsd' ), # Arch Linux, Debian, Ubuntu
226+ ]
227+ virtiofsd = utils .find_first_file (qemu_prefix , virtiofsd_locations )
228+
229+ # Prepare QEMU arguments
230+ self ._qemu_args += [
231+ '-chardev' , f"socket,id=char0,path={ self ._vfsd_conf ['files' ]['sock' ]} " ,
232+ '-device' , 'vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=shared' ,
233+ '-object' , f"memory-backend-file,id=shm,mem-path={ self ._vfsd_conf ['files' ]['mem' ]} ,share=on,size={ self .memory } " ,
234+ '-numa' , 'node,memdev=shm' ,
235+ ] # yapf: disable
236+
237+ self ._vfsd_conf ['cmd' ] = [
238+ sudo ,
239+ virtiofsd ,
240+ f"--socket-group={ grp .getgrgid (os .getgid ()).gr_name } " ,
241+ f"--socket-path={ self ._vfsd_conf ['files' ]['sock' ]} " ,
242+ '-o' , f"source={ SHARED_FOLDER } " ,
243+ '-o' , 'cache=always' ,
244+ ] # yapf: disable
245+
176246 def _prepare_initrd (self ):
177247 if not self ._initrd_arch :
178248 raise RuntimeError ('No initrd architecture specified?' )
179249 return utils .prepare_initrd (self ._initrd_arch ,
180250 gh_json_file = self .gh_json_file )
181251
182252 def _run_fg (self ):
253+ if self .share_folder_with_guest :
254+ self ._prepare_for_shared_folder ()
255+
183256 # Pretty print and run QEMU command
184257 qemu_cmd = []
185258
@@ -192,15 +265,32 @@ def _run_fg(self):
192265
193266 qemu_cmd += [self ._qemu_path , * self ._qemu_args ]
194267
195- print (f"$ { ' ' .join (shlex .quote (str (elem )) for elem in qemu_cmd )} " )
196- try :
197- subprocess .run (qemu_cmd , check = True )
198- except subprocess .CalledProcessError as err :
199- if err .returncode == 124 :
200- utils .red ("ERROR: QEMU timed out!" )
201- else :
202- utils .red ("ERROR: QEMU did not exit cleanly!" )
203- sys .exit (err .returncode )
268+ print (f"\n $ { ' ' .join (shlex .quote (str (elem )) for elem in qemu_cmd )} " )
269+ null_cm = contextlib .nullcontext ()
270+ with self ._vfsd_conf ['files' ]['log' ].open ('w' , encoding = 'utf-8' ) if self .share_folder_with_guest else null_cm as vfsd_log , \
271+ subprocess .Popen (self ._vfsd_conf ['cmd' ], stderr = vfsd_log , stdout = vfsd_log ) if self .share_folder_with_guest else null_cm as vfsd_proc :
272+ try :
273+ subprocess .run (qemu_cmd , check = True )
274+ except subprocess .CalledProcessError as err :
275+ if err .returncode == 124 :
276+ utils .red ("ERROR: QEMU timed out!" )
277+ else :
278+ utils .red ("ERROR: QEMU did not exit cleanly!" )
279+ # If virtiofsd is dead, it is pretty likely that it was the
280+ # cause of QEMU failing so add to the existing exception using
281+ # 'from'.
282+ if vfsd_proc and vfsd_proc .poll ():
283+ # yapf: disable
284+ vfsd_log_txt = self ._vfsd_conf ['files' ]['log' ].read_text (encoding = 'utf-8' )
285+ raise RuntimeError (f"virtiofsd failed with: { vfsd_log_txt } " ) from err
286+ # yapf: enable
287+ sys .exit (err .returncode )
288+ finally :
289+ if vfsd_proc :
290+ vfsd_proc .kill ()
291+ # Delete the memory to save space, it does not have to be
292+ # persistent
293+ self ._vfsd_conf ['files' ]['mem' ].unlink (missing_ok = True )
204294
205295 def _run_gdb (self ):
206296 qemu_cmd = [self ._qemu_path , * self ._qemu_args ]
@@ -846,6 +936,12 @@ def parse_arguments():
846936 help =
847937 'Number of processors for virtual machine (default: only KVM machines will use multiple vCPUs.)' ,
848938 )
939+ parser .add_argument (
940+ '--share-folder-with-guest' ,
941+ action = 'store_true' ,
942+ help =
943+ f"Share { SHARED_FOLDER } with the guest using virtiofs (requires interactive, not supported with gdb)." ,
944+ )
849945 parser .add_argument ('-t' ,
850946 '--timeout' ,
851947 default = '3m' ,
@@ -918,6 +1014,18 @@ def parse_arguments():
9181014 if args .no_kvm :
9191015 runner .use_kvm = False
9201016
1017+ if args .share_folder_with_guest :
1018+ if args .gdb :
1019+ utils .yellow (
1020+ 'Shared folder requested during a debugging session, ignoring...'
1021+ )
1022+ elif not args .interactive :
1023+ utils .yellow (
1024+ 'Shared folder requested without an interactive session, ignoring...'
1025+ )
1026+ else :
1027+ runner .share_folder_with_guest = True
1028+
9211029 if args .smp :
9221030 runner .smp = args .smp
9231031
0 commit comments