3
3
4
4
from argparse import ArgumentParser
5
5
import contextlib
6
+ import grp
6
7
import os
7
8
from pathlib import Path
8
9
import platform
14
15
15
16
import utils
16
17
18
+ SHARED_FOLDER = Path (utils .BOOT_UTILS , 'shared' )
17
19
SUPPORTED_ARCHES = [
18
20
'arm' ,
19
21
'arm32_v5' ,
@@ -40,7 +42,7 @@ class QEMURunner:
40
42
41
43
def __init__ (self ):
42
44
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)
44
46
self .cmdline = []
45
47
self .efi = False
46
48
self .gdb = False
@@ -51,14 +53,15 @@ def __init__(self):
51
53
self .kernel_config = None
52
54
self .kernel_dir = None
53
55
self .memory = '512m'
56
+ self .share_folder_with_guest = False
57
+ self .smp = 0
54
58
self .supports_efi = False
59
+ self .timeout = ''
55
60
# It may be tempting to use self.use_kvm during initialization of
56
61
# subclasses to set certain properties but the user can explicitly opt
57
62
# out of KVM after instantiation, so any decisions based on it should
58
63
# be confined to run().
59
64
self .use_kvm = False
60
- self .smp = 0
61
- self .timeout = ''
62
65
63
66
self ._default_kernel_path = None
64
67
self ._dtbs = []
@@ -72,6 +75,14 @@ def __init__(self):
72
75
'-nodefaults' ,
73
76
] # yapf: disable
74
77
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
+ }
75
86
76
87
def _find_dtb (self ):
77
88
if not self ._dtbs :
@@ -173,13 +184,75 @@ def _get_qemu_ver_tuple(self):
173
184
def _have_dev_kvm_access (self ):
174
185
return os .access ('/dev/kvm' , os .R_OK | os .W_OK )
175
186
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/[email protected] /
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 ._ram } " ,
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
+
176
246
def _prepare_initrd (self ):
177
247
if not self ._initrd_arch :
178
248
raise RuntimeError ('No initrd architecture specified?' )
179
249
return utils .prepare_initrd (self ._initrd_arch ,
180
250
gh_json_file = self .gh_json_file )
181
251
182
252
def _run_fg (self ):
253
+ if self .share_folder_with_guest :
254
+ self ._prepare_for_shared_folder ()
255
+
183
256
# Pretty print and run QEMU command
184
257
qemu_cmd = []
185
258
@@ -192,15 +265,32 @@ def _run_fg(self):
192
265
193
266
qemu_cmd += [self ._qemu_path , * self ._qemu_args ]
194
267
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 )
204
294
205
295
def _run_gdb (self ):
206
296
qemu_cmd = [self ._qemu_path , * self ._qemu_args ]
@@ -846,6 +936,12 @@ def parse_arguments():
846
936
help =
847
937
'Number of processors for virtual machine (default: only KVM machines will use multiple vCPUs.)' ,
848
938
)
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
+ )
849
945
parser .add_argument ('-t' ,
850
946
'--timeout' ,
851
947
default = '3m' ,
@@ -918,6 +1014,18 @@ def parse_arguments():
918
1014
if args .no_kvm :
919
1015
runner .use_kvm = False
920
1016
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
+
921
1029
if args .smp :
922
1030
runner .smp = args .smp
923
1031
0 commit comments