summaryrefslogtreecommitdiffstats
path: root/scripts/runqemu
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-xscripts/runqemu625
1 files changed, 417 insertions, 208 deletions
diff --git a/scripts/runqemu b/scripts/runqemu
index 532f2e338d..69cd44864e 100755
--- a/scripts/runqemu
+++ b/scripts/runqemu
@@ -18,6 +18,7 @@ import shutil
18import glob 18import glob
19import configparser 19import configparser
20import signal 20import signal
21import time
21 22
22class RunQemuError(Exception): 23class RunQemuError(Exception):
23 """Custom exception to raise on known errors.""" 24 """Custom exception to raise on known errors."""
@@ -65,20 +66,25 @@ of the following environment variables (in any order):
65 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) 66 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified)
66 Simplified QEMU command-line options can be passed with: 67 Simplified QEMU command-line options can be passed with:
67 nographic - disable video console 68 nographic - disable video console
69 nonetwork - disable network connectivity
68 novga - Disable VGA emulation completely 70 novga - Disable VGA emulation completely
69 sdl - choose the SDL UI frontend 71 sdl - choose the SDL UI frontend
70 gtk - choose the Gtk UI frontend 72 gtk - choose the Gtk UI frontend
71 gl - enable virgl-based GL acceleration (also needs gtk or sdl options) 73 gl - enable virgl-based GL acceleration (also needs gtk or sdl options)
72 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) 74 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options)
73 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it 75 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it
76 (hint: if /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create
77 one suitable for mesa llvmpipe software renderer)
74 serial - enable a serial console on /dev/ttyS0 78 serial - enable a serial console on /dev/ttyS0
75 serialstdio - enable a serial console on the console (regardless of graphics mode) 79 serialstdio - enable a serial console on the console (regardless of graphics mode)
76 slirp - enable user networking, no root privileges is required 80 slirp - enable user networking, no root privilege is required
77 snapshot - don't write changes to back to images 81 snapshot - don't write changes back to images
78 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required) 82 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required)
79 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required) 83 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required)
80 publicvnc - enable a VNC server open to all hosts 84 publicvnc - enable a VNC server open to all hosts
81 audio - enable audio 85 audio - enable audio
86 guestagent - enable guest agent communication
87 qmp=<path> - create a QMP socket (defaults to unix:qmp.sock if unspecified)
82 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI 88 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI
83 tcpserial=<port> - specify tcp serial port number 89 tcpserial=<port> - specify tcp serial port number
84 qemuparams=<xyz> - specify custom parameters to QEMU 90 qemuparams=<xyz> - specify custom parameters to QEMU
@@ -113,10 +119,10 @@ def check_tun():
113 if not os.access(dev_tun, os.W_OK): 119 if not os.access(dev_tun, os.W_OK):
114 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) 120 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun))
115 121
116def get_first_file(cmds): 122def get_first_file(globs):
117 """Return first file found in wildcard cmds""" 123 """Return first file found in wildcard globs"""
118 for cmd in cmds: 124 for g in globs:
119 all_files = glob.glob(cmd) 125 all_files = glob.glob(g)
120 if all_files: 126 if all_files:
121 for f in all_files: 127 for f in all_files:
122 if not os.path.isdir(f): 128 if not os.path.isdir(f):
@@ -139,12 +145,12 @@ class BaseConfig(object):
139 'OE_TMPDIR', 145 'OE_TMPDIR',
140 'OECORE_NATIVE_SYSROOT', 146 'OECORE_NATIVE_SYSROOT',
141 'MULTICONFIG', 147 'MULTICONFIG',
148 'SERIAL_CONSOLES',
142 ) 149 )
143 150
144 self.qemu_opt = '' 151 self.qemu_opt = ''
145 self.qemu_opt_script = '' 152 self.qemu_opt_script = ''
146 self.qemuparams = '' 153 self.qemuparams = ''
147 self.clean_nfs_dir = False
148 self.nfs_server = '' 154 self.nfs_server = ''
149 self.rootfs = '' 155 self.rootfs = ''
150 # File name(s) of a OVMF firmware file or variable store, 156 # File name(s) of a OVMF firmware file or variable store,
@@ -173,6 +179,15 @@ class BaseConfig(object):
173 self.nfs_running = False 179 self.nfs_running = False
174 self.serialconsole = False 180 self.serialconsole = False
175 self.serialstdio = False 181 self.serialstdio = False
182 self.nographic = False
183 self.nonetwork = False
184 self.sdl = False
185 self.gtk = False
186 self.gl = False
187 self.gl_es = False
188 self.egl_headless = False
189 self.publicvnc = False
190 self.novga = False
176 self.cleantap = False 191 self.cleantap = False
177 self.saved_stty = '' 192 self.saved_stty = ''
178 self.audio_enabled = False 193 self.audio_enabled = False
@@ -184,12 +199,14 @@ class BaseConfig(object):
184 self.snapshot = False 199 self.snapshot = False
185 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") 200 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx")
186 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 201 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs',
187 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz') 202 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz',
203 'squashfs', 'squashfs-xz', 'squashfs-lzo',
204 'squashfs-lz4', 'squashfs-zst')
188 self.vmtypes = ('hddimg', 'iso') 205 self.vmtypes = ('hddimg', 'iso')
189 self.fsinfo = {} 206 self.fsinfo = {}
190 self.network_device = "-device e1000,netdev=net0,mac=@MAC@" 207 self.network_device = "-device e1000,netdev=net0,mac=@MAC@"
191 self.cmdline_ip_slirp = "ip=dhcp" 208 self.cmdline_ip_slirp = "ip=dhcp"
192 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0" 209 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0::eth0:off:8.8.8.8 net.ifnames=0"
193 # Use different mac section for tap and slirp to avoid 210 # Use different mac section for tap and slirp to avoid
194 # conflicts, e.g., when one is running with tap, the other is 211 # conflicts, e.g., when one is running with tap, the other is
195 # running with slirp. 212 # running with slirp.
@@ -199,9 +216,15 @@ class BaseConfig(object):
199 self.mac_tap = "52:54:00:12:34:" 216 self.mac_tap = "52:54:00:12:34:"
200 self.mac_slirp = "52:54:00:12:35:" 217 self.mac_slirp = "52:54:00:12:35:"
201 # pid of the actual qemu process 218 # pid of the actual qemu process
202 self.qemupid = None 219 self.qemu_environ = os.environ.copy()
220 self.qemuprocess = None
203 # avoid cleanup twice 221 # avoid cleanup twice
204 self.cleaned = False 222 self.cleaned = False
223 # Files to cleanup after run
224 self.cleanup_files = []
225 self.qmp = None
226 self.guest_agent = False
227 self.guest_agent_sockpath = '/tmp/qga.sock'
205 228
206 def acquire_taplock(self, error=True): 229 def acquire_taplock(self, error=True):
207 logger.debug("Acquiring lockfile %s..." % self.taplock) 230 logger.debug("Acquiring lockfile %s..." % self.taplock)
@@ -223,9 +246,12 @@ class BaseConfig(object):
223 def release_taplock(self): 246 def release_taplock(self):
224 if self.taplock_descriptor: 247 if self.taplock_descriptor:
225 logger.debug("Releasing lockfile for tap device '%s'" % self.tap) 248 logger.debug("Releasing lockfile for tap device '%s'" % self.tap)
226 fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN) 249 # We pass the fd to the qemu process and if we unlock here, it would unlock for
250 # that too. Therefore don't unlock, just close
251 # fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN)
227 self.taplock_descriptor.close() 252 self.taplock_descriptor.close()
228 os.remove(self.taplock) 253 # Removing the file is a potential race, don't do that either
254 # os.remove(self.taplock)
229 self.taplock_descriptor = None 255 self.taplock_descriptor = None
230 256
231 def check_free_port(self, host, port, lockdir): 257 def check_free_port(self, host, port, lockdir):
@@ -263,17 +289,23 @@ class BaseConfig(object):
263 289
264 def release_portlock(self, lockfile=None): 290 def release_portlock(self, lockfile=None):
265 if lockfile != None: 291 if lockfile != None:
266 logger.debug("Releasing lockfile '%s'" % lockfile) 292 logger.debug("Releasing lockfile '%s'" % lockfile)
267 fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) 293 # We pass the fd to the qemu process and if we unlock here, it would unlock for
268 self.portlocks[lockfile].close() 294 # that too. Therefore don't unlock, just close
269 os.remove(lockfile) 295 # fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN)
270 del self.portlocks[lockfile] 296 self.portlocks[lockfile].close()
297 # Removing the file is a potential race, don't do that either
298 # os.remove(lockfile)
299 del self.portlocks[lockfile]
271 elif len(self.portlocks): 300 elif len(self.portlocks):
272 for lockfile, descriptor in self.portlocks.items(): 301 for lockfile, descriptor in self.portlocks.items():
273 logger.debug("Releasing lockfile '%s'" % lockfile) 302 logger.debug("Releasing lockfile '%s'" % lockfile)
274 fcntl.flock(descriptor, fcntl.LOCK_UN) 303 # We pass the fd to the qemu process and if we unlock here, it would unlock for
304 # that too. Therefore don't unlock, just close
305 # fcntl.flock(descriptor, fcntl.LOCK_UN)
275 descriptor.close() 306 descriptor.close()
276 os.remove(lockfile) 307 # Removing the file is a potential race, don't do that either
308 # os.remove(lockfile)
277 self.portlocks = {} 309 self.portlocks = {}
278 310
279 def get(self, key): 311 def get(self, key):
@@ -331,21 +363,21 @@ class BaseConfig(object):
331 def check_arg_path(self, p): 363 def check_arg_path(self, p):
332 """ 364 """
333 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf 365 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf
334 - Check whether is a kernel file 366 - Check whether it is a kernel file
335 - Check whether is a image file 367 - Check whether it is an image file
336 - Check whether it is a nfs dir 368 - Check whether it is an NFS dir
337 - Check whether it is a OVMF flash file 369 - Check whether it is an OVMF flash file
338 """ 370 """
339 if p.endswith('.qemuboot.conf'): 371 if p.endswith('.qemuboot.conf'):
340 self.qemuboot = p 372 self.qemuboot = p
341 self.qbconfload = True 373 self.qbconfload = True
342 elif re.search('\.bin$', p) or re.search('bzImage', p) or \ 374 elif re.search('\\.bin$', p) or re.search('bzImage', p) or \
343 re.search('zImage', p) or re.search('vmlinux', p) or \ 375 re.search('zImage', p) or re.search('vmlinux', p) or \
344 re.search('fitImage', p) or re.search('uImage', p): 376 re.search('fitImage', p) or re.search('uImage', p):
345 self.kernel = p 377 self.kernel = p
346 elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p): 378 elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)):
347 self.rootfs = p 379 self.rootfs = p
348 # Check filename against self.fstypes can hanlde <file>.cpio.gz, 380 # Check filename against self.fstypes can handle <file>.cpio.gz,
349 # otherwise, its type would be "gz", which is incorrect. 381 # otherwise, its type would be "gz", which is incorrect.
350 fst = "" 382 fst = ""
351 for t in self.fstypes: 383 for t in self.fstypes:
@@ -353,18 +385,24 @@ class BaseConfig(object):
353 fst = t 385 fst = t
354 break 386 break
355 if not fst: 387 if not fst:
356 m = re.search('.*\.(.*)$', self.rootfs) 388 m = re.search('.*\\.(.*)$', self.rootfs)
357 if m: 389 if m:
358 fst = m.group(1) 390 fst = m.group(1)
359 if fst: 391 if fst:
360 self.check_arg_fstype(fst) 392 self.check_arg_fstype(fst)
361 qb = re.sub('\.' + fst + "$", '', self.rootfs) 393 qb = re.sub('\\.' + fst + "$", '.qemuboot.conf', self.rootfs)
362 qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf')
363 if os.path.exists(qb): 394 if os.path.exists(qb):
364 self.qemuboot = qb 395 self.qemuboot = qb
365 self.qbconfload = True 396 self.qbconfload = True
366 else: 397 else:
367 logger.warning("%s doesn't exist" % qb) 398 logger.warning("%s doesn't exist, will try to remove '.rootfs' from filename" % qb)
399 # They to remove .rootfs (IMAGE_NAME_SUFFIX) as well
400 qb = re.sub('\\.rootfs.qemuboot.conf$', '.qemuboot.conf', qb)
401 if os.path.exists(qb):
402 self.qemuboot = qb
403 self.qbconfload = True
404 else:
405 logger.warning("%s doesn't exist" % qb)
368 else: 406 else:
369 raise RunQemuError("Can't find FSTYPE from: %s" % p) 407 raise RunQemuError("Can't find FSTYPE from: %s" % p)
370 408
@@ -398,6 +436,7 @@ class BaseConfig(object):
398 # are there other scenarios in which we need to support being 436 # are there other scenarios in which we need to support being
399 # invoked by bitbake? 437 # invoked by bitbake?
400 deploy = self.get('DEPLOY_DIR_IMAGE') 438 deploy = self.get('DEPLOY_DIR_IMAGE')
439 image_link_name = self.get('IMAGE_LINK_NAME')
401 bbchild = deploy and self.get('OE_TMPDIR') 440 bbchild = deploy and self.get('OE_TMPDIR')
402 if bbchild: 441 if bbchild:
403 self.set_machine_deploy_dir(arg, deploy) 442 self.set_machine_deploy_dir(arg, deploy)
@@ -422,23 +461,24 @@ class BaseConfig(object):
422 else: 461 else:
423 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) 462 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image)
424 self.set("MACHINE", arg) 463 self.set("MACHINE", arg)
464 if not image_link_name:
465 s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M)
466 if s:
467 image_link_name = s.group(1)
468 self.set("IMAGE_LINK_NAME", image_link_name)
469 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
425 470
426 def set_dri_path(self): 471 def set_dri_path(self):
427 # As runqemu can be run within bitbake (when using testimage, for example), 472 drivers_path = os.path.join(self.bindir_native, '../lib/dri')
428 # we need to ensure that we run host pkg-config, and that it does not 473 if not os.path.exists(drivers_path) or not os.listdir(drivers_path):
429 # get mis-directed to native build paths set by bitbake. 474 raise RunQemuError("""
430 try: 475qemu has been built without opengl support and accelerated graphics support is not available.
431 del os.environ['PKG_CONFIG_PATH'] 476To enable it, add:
432 del os.environ['PKG_CONFIG_DIR'] 477DISTRO_FEATURES_NATIVE:append = " opengl"
433 del os.environ['PKG_CONFIG_LIBDIR'] 478DISTRO_FEATURES_NATIVESDK:append = " opengl"
434 del os.environ['PKG_CONFIG_SYSROOT_DIR'] 479to your build configuration.
435 except KeyError: 480""")
436 pass 481 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path
437 try:
438 dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True)
439 except subprocess.CalledProcessError as e:
440 raise RunQemuError("Could not determine the path to dri drivers on the host via pkg-config.\nPlease install Mesa development files (particularly, dri.pc) on the host machine.")
441 os.environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip()
442 482
443 def check_args(self): 483 def check_args(self):
444 for debug in ("-d", "--debug"): 484 for debug in ("-d", "--debug"):
@@ -452,51 +492,32 @@ class BaseConfig(object):
452 sys.argv.remove(quiet) 492 sys.argv.remove(quiet)
453 493
454 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: 494 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]:
455 os.environ['SDL_RENDER_DRIVER'] = 'software' 495 self.qemu_environ['SDL_RENDER_DRIVER'] = 'software'
496 self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false'
456 497
457 unknown_arg = "" 498 unknown_arg = ""
458 for arg in sys.argv[1:]: 499 for arg in sys.argv[1:]:
459 if arg in self.fstypes + self.vmtypes + self.wictypes: 500 if arg in self.fstypes + self.vmtypes + self.wictypes:
460 self.check_arg_fstype(arg) 501 self.check_arg_fstype(arg)
461 elif arg == 'nographic': 502 elif arg == 'nographic':
462 if ('sdl' in sys.argv): 503 self.nographic = True
463 raise RunQemuError('Option nographic makes no sense alongside the sdl option.' % (arg)) 504 elif arg == "nonetwork":
464 if ('gtk' in sys.argv): 505 self.nonetwork = True
465 raise RunQemuError('Option nographic makes no sense alongside the gtk option.' % (arg))
466 self.qemu_opt_script += ' -nographic'
467 self.kernel_cmdline_script += ' console=ttyS0'
468 elif arg == 'sdl': 506 elif arg == 'sdl':
469 if 'gl' in sys.argv[1:]: 507 self.sdl = True
470 self.set_dri_path()
471 self.qemu_opt_script += ' -vga virtio -display sdl,gl=on,show-cursor=on'
472 elif 'gl-es' in sys.argv[1:]:
473 self.set_dri_path()
474 self.qemu_opt_script += ' -vga virtio -display sdl,gl=es,show-cursor=on'
475 else:
476 self.qemu_opt_script += ' -display sdl,show-cursor=on'
477 elif arg == 'gtk': 508 elif arg == 'gtk':
478 if 'gl' in sys.argv[1:]: 509 self.gtk = True
479 self.set_dri_path() 510 elif arg == 'gl':
480 self.qemu_opt_script += ' -vga virtio -display gtk,gl=on,show-cursor=on' 511 self.gl = True
481 elif 'gl-es' in sys.argv[1:]: 512 elif arg == 'gl-es':
482 self.set_dri_path() 513 self.gl_es = True
483 self.qemu_opt_script += ' -vga virtio -display gtk,gl=es,show-cursor=on'
484 else:
485 self.qemu_opt_script += ' -display gtk,show-cursor=on'
486 elif arg == 'gl' or arg == 'gl-es':
487 # These args are handled inside sdl or gtk blocks above
488 if ('gtk' not in sys.argv) and ('sdl' not in sys.argv):
489 raise RunQemuError('Option %s also needs gtk or sdl option.' % (arg))
490 elif arg == 'egl-headless': 514 elif arg == 'egl-headless':
491 self.set_dri_path() 515 self.egl_headless = True
492 self.qemu_opt_script += ' -vga virtio -display egl-headless,show-cursor=on'
493 elif arg == 'novga': 516 elif arg == 'novga':
494 self.qemu_opt_script += ' -vga none' 517 self.novga = True
495 elif arg == 'serial': 518 elif arg == 'serial':
496 self.kernel_cmdline_script += ' console=ttyS0'
497 self.serialconsole = True 519 self.serialconsole = True
498 elif arg == "serialstdio": 520 elif arg == "serialstdio":
499 self.kernel_cmdline_script += ' console=ttyS0'
500 self.serialstdio = True 521 self.serialstdio = True
501 elif arg == 'audio': 522 elif arg == 'audio':
502 logger.info("Enabling audio in qemu") 523 logger.info("Enabling audio in qemu")
@@ -513,7 +534,16 @@ class BaseConfig(object):
513 elif arg == 'snapshot': 534 elif arg == 'snapshot':
514 self.snapshot = True 535 self.snapshot = True
515 elif arg == 'publicvnc': 536 elif arg == 'publicvnc':
537 self.publicvnc = True
516 self.qemu_opt_script += ' -vnc :0' 538 self.qemu_opt_script += ' -vnc :0'
539 elif arg == 'guestagent':
540 self.guest_agent = True
541 elif arg == "qmp":
542 self.qmp = "unix:qmp.sock"
543 elif arg.startswith("qmp="):
544 self.qmp = arg[len('qmp='):]
545 elif arg.startswith('guestagent-sockpath='):
546 self.guest_agent_sockpath = '%s' % arg[len('guestagent-sockpath='):]
517 elif arg.startswith('tcpserial='): 547 elif arg.startswith('tcpserial='):
518 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] 548 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):]
519 elif arg.startswith('qemuparams='): 549 elif arg.startswith('qemuparams='):
@@ -545,21 +575,28 @@ class BaseConfig(object):
545 self.check_arg_machine(unknown_arg) 575 self.check_arg_machine(unknown_arg)
546 576
547 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): 577 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload):
548 self.load_bitbake_env() 578 self.load_bitbake_env(target=self.rootfs)
549 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 579 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
550 if s: 580 if s:
551 self.set("DEPLOY_DIR_IMAGE", s.group(1)) 581 self.set("DEPLOY_DIR_IMAGE", s.group(1))
552 582
583 if not self.get('IMAGE_LINK_NAME') and self.rootfs:
584 s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M)
585 if s:
586 image_link_name = s.group(1)
587 self.set("IMAGE_LINK_NAME", image_link_name)
588 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
589
553 def check_kvm(self): 590 def check_kvm(self):
554 """Check kvm and kvm-host""" 591 """Check kvm and kvm-host"""
555 if not (self.kvm_enabled or self.vhost_enabled): 592 if not (self.kvm_enabled or self.vhost_enabled):
556 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU')) 593 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP'))
557 return 594 return
558 595
559 if not self.get('QB_CPU_KVM'): 596 if not self.get('QB_CPU_KVM'):
560 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") 597 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm")
561 598
562 self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM')) 599 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP'))
563 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" 600 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu"
564 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" 601 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM"
565 dev_kvm = '/dev/kvm' 602 dev_kvm = '/dev/kvm'
@@ -579,11 +616,6 @@ class BaseConfig(object):
579 616
580 if os.access(dev_kvm, os.W_OK|os.R_OK): 617 if os.access(dev_kvm, os.W_OK|os.R_OK):
581 self.qemu_opt_script += ' -enable-kvm' 618 self.qemu_opt_script += ' -enable-kvm'
582 if self.get('MACHINE') == "qemux86":
583 # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs
584 # See YOCTO #12301
585 # On 64 bit we use x2apic
586 self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic"
587 else: 619 else:
588 logger.error("You have no read or write permission on /dev/kvm.") 620 logger.error("You have no read or write permission on /dev/kvm.")
589 logger.error("Please change the ownership of this file as described at:") 621 logger.error("Please change the ownership of this file as described at:")
@@ -624,10 +656,10 @@ class BaseConfig(object):
624 elif fsflag == 'kernel-in-fs': 656 elif fsflag == 'kernel-in-fs':
625 wic_fs = False 657 wic_fs = False
626 else: 658 else:
627 logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) 659 logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag)
628 continue 660 continue
629 else: 661 else:
630 logger.warn('QB_FSINFO is not supported for image type "%s"', fstype) 662 logger.warning('QB_FSINFO is not supported for image type "%s"', fstype)
631 continue 663 continue
632 664
633 if fstype in self.fsinfo: 665 if fstype in self.fsinfo:
@@ -660,16 +692,16 @@ class BaseConfig(object):
660 692
661 if self.rootfs and not os.path.exists(self.rootfs): 693 if self.rootfs and not os.path.exists(self.rootfs):
662 # Lazy rootfs 694 # Lazy rootfs
663 self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), 695 self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'),
664 self.rootfs, self.get('MACHINE'), 696 self.get('IMAGE_LINK_NAME'),
665 self.fstype) 697 self.fstype)
666 elif not self.rootfs: 698 elif not self.rootfs:
667 cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) 699 glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype)
668 cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) 700 glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype)
669 cmds = (cmd_name, cmd_link) 701 globs = (glob_name, glob_link)
670 self.rootfs = get_first_file(cmds) 702 self.rootfs = get_first_file(globs)
671 if not self.rootfs: 703 if not self.rootfs:
672 raise RunQemuError("Failed to find rootfs: %s or %s" % cmds) 704 raise RunQemuError("Failed to find rootfs: %s or %s" % globs)
673 705
674 if not os.path.exists(self.rootfs): 706 if not os.path.exists(self.rootfs):
675 raise RunQemuError("Can't find rootfs: %s" % self.rootfs) 707 raise RunQemuError("Can't find rootfs: %s" % self.rootfs)
@@ -729,10 +761,10 @@ class BaseConfig(object):
729 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) 761 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name)
730 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 762 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
731 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 763 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
732 cmds = (kernel_match_name, kernel_match_link, kernel_startswith) 764 globs = (kernel_match_name, kernel_match_link, kernel_startswith)
733 self.kernel = get_first_file(cmds) 765 self.kernel = get_first_file(globs)
734 if not self.kernel: 766 if not self.kernel:
735 raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds) 767 raise RunQemuError('KERNEL not found: %s, %s or %s' % globs)
736 768
737 if not os.path.exists(self.kernel): 769 if not os.path.exists(self.kernel):
738 raise RunQemuError("KERNEL %s not found" % self.kernel) 770 raise RunQemuError("KERNEL %s not found" % self.kernel)
@@ -749,13 +781,13 @@ class BaseConfig(object):
749 dtb = self.get('QB_DTB') 781 dtb = self.get('QB_DTB')
750 if dtb: 782 if dtb:
751 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 783 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
752 cmd_match = "%s/%s" % (deploy_dir_image, dtb) 784 glob_match = "%s/%s" % (deploy_dir_image, dtb)
753 cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb) 785 glob_startswith = "%s/%s*" % (deploy_dir_image, dtb)
754 cmd_wild = "%s/*.dtb" % deploy_dir_image 786 glob_wild = "%s/*.dtb" % deploy_dir_image
755 cmds = (cmd_match, cmd_startswith, cmd_wild) 787 globs = (glob_match, glob_startswith, glob_wild)
756 self.dtb = get_first_file(cmds) 788 self.dtb = get_first_file(globs)
757 if not os.path.exists(self.dtb): 789 if not os.path.exists(self.dtb):
758 raise RunQemuError('DTB not found: %s, %s or %s' % cmds) 790 raise RunQemuError('DTB not found: %s, %s or %s' % globs)
759 791
760 def check_bios(self): 792 def check_bios(self):
761 """Check and set bios""" 793 """Check and set bios"""
@@ -779,7 +811,7 @@ class BaseConfig(object):
779 raise RunQemuError('BIOS not found: %s' % bios_match_name) 811 raise RunQemuError('BIOS not found: %s' % bios_match_name)
780 812
781 if not os.path.exists(self.bios): 813 if not os.path.exists(self.bios):
782 raise RunQemuError("KERNEL %s not found" % self.bios) 814 raise RunQemuError("BIOS %s not found" % self.bios)
783 815
784 816
785 def check_mem(self): 817 def check_mem(self):
@@ -806,7 +838,7 @@ class BaseConfig(object):
806 self.set('QB_MEM', qb_mem) 838 self.set('QB_MEM', qb_mem)
807 839
808 mach = self.get('MACHINE') 840 mach = self.get('MACHINE')
809 if not mach.startswith('qemumips'): 841 if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')):
810 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' 842 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M'
811 843
812 self.qemu_opt_script += ' %s' % self.get('QB_MEM') 844 self.qemu_opt_script += ' %s' % self.get('QB_MEM')
@@ -818,11 +850,11 @@ class BaseConfig(object):
818 if self.get('QB_TCPSERIAL_OPT'): 850 if self.get('QB_TCPSERIAL_OPT'):
819 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) 851 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port)
820 else: 852 else:
821 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 853 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
822 854
823 if len(ports) > 1: 855 if len(ports) > 1:
824 for port in ports[1:]: 856 for port in ports[1:]:
825 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 857 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
826 858
827 def check_and_set(self): 859 def check_and_set(self):
828 """Check configs sanity and set when needed""" 860 """Check configs sanity and set when needed"""
@@ -865,8 +897,10 @@ class BaseConfig(object):
865 machine = self.get('MACHINE') 897 machine = self.get('MACHINE')
866 if not machine: 898 if not machine:
867 machine = os.path.basename(deploy_dir_image) 899 machine = os.path.basename(deploy_dir_image)
868 self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image, 900 if not self.get('IMAGE_LINK_NAME'):
869 self.rootfs, machine) 901 raise RunQemuError("IMAGE_LINK_NAME wasn't set to find corresponding .qemuboot.conf file")
902 self.qemuboot = "%s/%s.qemuboot.conf" % (deploy_dir_image,
903 self.get('IMAGE_LINK_NAME'))
870 else: 904 else:
871 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image 905 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image
872 logger.debug('Running %s...' % cmd) 906 logger.debug('Running %s...' % cmd)
@@ -987,19 +1021,16 @@ class BaseConfig(object):
987 if self.slirp_enabled: 1021 if self.slirp_enabled:
988 self.nfs_server = '10.0.2.2' 1022 self.nfs_server = '10.0.2.2'
989 else: 1023 else:
990 self.nfs_server = '192.168.7.1' 1024 self.nfs_server = '192.168.7.@GATEWAY@'
991 1025
992 # Figure out a new nfs_instance to allow multiple qemus running. 1026 nfsd_port = 3048 + self.nfs_instance
993 ps = subprocess.check_output(("ps", "auxww")).decode('utf-8') 1027 lockdir = "/tmp/qemu-port-locks"
994 pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) ' 1028 self.make_lock_dir(lockdir)
995 all_instances = re.findall(pattern, ps, re.M) 1029 while not self.check_free_port('localhost', nfsd_port, lockdir):
996 if all_instances: 1030 self.nfs_instance += 1
997 all_instances.sort(key=int) 1031 nfsd_port += 1
998 self.nfs_instance = int(all_instances.pop()) + 1
999
1000 nfsd_port = 3049 + 2 * self.nfs_instance
1001 mountd_port = 3048 + 2 * self.nfs_instance
1002 1032
1033 mountd_port = nfsd_port
1003 # Export vars for runqemu-export-rootfs 1034 # Export vars for runqemu-export-rootfs
1004 export_dict = { 1035 export_dict = {
1005 'NFS_INSTANCE': self.nfs_instance, 1036 'NFS_INSTANCE': self.nfs_instance,
@@ -1010,7 +1041,11 @@ class BaseConfig(object):
1010 # Use '%s' since they are integers 1041 # Use '%s' since they are integers
1011 os.putenv(k, '%s' % v) 1042 os.putenv(k, '%s' % v)
1012 1043
1013 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port) 1044 qb_nfsrootfs_extra_opt = self.get("QB_NFSROOTFS_EXTRA_OPT")
1045 if qb_nfsrootfs_extra_opt and not qb_nfsrootfs_extra_opt.startswith(","):
1046 qb_nfsrootfs_extra_opt = "," + qb_nfsrootfs_extra_opt
1047
1048 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s%s" % (nfsd_port, mountd_port, qb_nfsrootfs_extra_opt)
1014 1049
1015 # Extract .tar.bz2 or .tar.bz if no nfs dir 1050 # Extract .tar.bz2 or .tar.bz if no nfs dir
1016 if not (self.rootfs and os.path.isdir(self.rootfs)): 1051 if not (self.rootfs and os.path.isdir(self.rootfs)):
@@ -1033,22 +1068,41 @@ class BaseConfig(object):
1033 cmd = ('runqemu-extract-sdk', src, dest) 1068 cmd = ('runqemu-extract-sdk', src, dest)
1034 logger.info('Running %s...' % str(cmd)) 1069 logger.info('Running %s...' % str(cmd))
1035 if subprocess.call(cmd) != 0: 1070 if subprocess.call(cmd) != 0:
1036 raise RunQemuError('Failed to run %s' % cmd) 1071 raise RunQemuError('Failed to run %s' % str(cmd))
1037 self.clean_nfs_dir = True
1038 self.rootfs = dest 1072 self.rootfs = dest
1073 self.cleanup_files.append(self.rootfs)
1074 self.cleanup_files.append('%s.pseudo_state' % self.rootfs)
1039 1075
1040 # Start the userspace NFS server 1076 # Start the userspace NFS server
1041 cmd = ('runqemu-export-rootfs', 'start', self.rootfs) 1077 cmd = ('runqemu-export-rootfs', 'start', self.rootfs)
1042 logger.info('Running %s...' % str(cmd)) 1078 logger.info('Running %s...' % str(cmd))
1043 if subprocess.call(cmd) != 0: 1079 if subprocess.call(cmd) != 0:
1044 raise RunQemuError('Failed to run %s' % cmd) 1080 raise RunQemuError('Failed to run %s' % str(cmd))
1045 1081
1046 self.nfs_running = True 1082 self.nfs_running = True
1047 1083
1084 def setup_cmd(self):
1085 cmd = self.get('QB_SETUP_CMD')
1086 if cmd != '':
1087 logger.info('Running setup command %s' % str(cmd))
1088 if subprocess.call(cmd, shell=True) != 0:
1089 raise RunQemuError('Failed to run %s' % str(cmd))
1090
1048 def setup_net_bridge(self): 1091 def setup_net_bridge(self):
1049 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( 1092 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % (
1050 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) 1093 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper')))
1051 1094
1095 def make_lock_dir(self, lockdir):
1096 if not os.path.exists(lockdir):
1097 # There might be a race issue when multi runqemu processess are
1098 # running at the same time.
1099 try:
1100 os.mkdir(lockdir)
1101 os.chmod(lockdir, 0o777)
1102 except FileExistsError:
1103 pass
1104 return
1105
1052 def setup_slirp(self): 1106 def setup_slirp(self):
1053 """Setup user networking""" 1107 """Setup user networking"""
1054 1108
@@ -1058,7 +1112,7 @@ class BaseConfig(object):
1058 logger.info("Network configuration:%s", netconf) 1112 logger.info("Network configuration:%s", netconf)
1059 self.kernel_cmdline_script += netconf 1113 self.kernel_cmdline_script += netconf
1060 # Port mapping 1114 # Port mapping
1061 hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23" 1115 hostfwd = ",hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23"
1062 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) 1116 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE'))
1063 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default 1117 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default
1064 # Figure out the port 1118 # Figure out the port
@@ -1067,14 +1121,7 @@ class BaseConfig(object):
1067 mac = 2 1121 mac = 2
1068 1122
1069 lockdir = "/tmp/qemu-port-locks" 1123 lockdir = "/tmp/qemu-port-locks"
1070 if not os.path.exists(lockdir): 1124 self.make_lock_dir(lockdir)
1071 # There might be a race issue when multi runqemu processess are
1072 # running at the same time.
1073 try:
1074 os.mkdir(lockdir)
1075 os.chmod(lockdir, 0o777)
1076 except FileExistsError:
1077 pass
1078 1125
1079 # Find a free port to avoid conflicts 1126 # Find a free port to avoid conflicts
1080 for p in ports[:]: 1127 for p in ports[:]:
@@ -1114,20 +1161,17 @@ class BaseConfig(object):
1114 logger.error("ip: %s" % ip) 1161 logger.error("ip: %s" % ip)
1115 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") 1162 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found")
1116 1163
1117 if not os.path.exists(lockdir): 1164 self.make_lock_dir(lockdir)
1118 # There might be a race issue when multi runqemu processess are
1119 # running at the same time.
1120 try:
1121 os.mkdir(lockdir)
1122 os.chmod(lockdir, 0o777)
1123 except FileExistsError:
1124 pass
1125 1165
1126 cmd = (ip, 'link') 1166 cmd = (ip, 'link')
1127 logger.debug('Running %s...' % str(cmd)) 1167 logger.debug('Running %s...' % str(cmd))
1128 ip_link = subprocess.check_output(cmd).decode('utf-8') 1168 ip_link = subprocess.check_output(cmd).decode('utf-8')
1129 # Matches line like: 6: tap0: <foo> 1169 # Matches line like: 6: tap0: <foo>
1130 possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M) 1170 oe_tap_name = 'tap'
1171 if 'OE_TAP_NAME' in os.environ:
1172 oe_tap_name = os.environ['OE_TAP_NAME']
1173 tap_re = '^[0-9]+: +(' + oe_tap_name + '[0-9]+): <.*'
1174 possibles = re.findall(tap_re, ip_link, re.M)
1131 tap = "" 1175 tap = ""
1132 for p in possibles: 1176 for p in possibles:
1133 lockfile = os.path.join(lockdir, p) 1177 lockfile = os.path.join(lockdir, p)
@@ -1150,7 +1194,7 @@ class BaseConfig(object):
1150 gid = os.getgid() 1194 gid = os.getgid()
1151 uid = os.getuid() 1195 uid = os.getuid()
1152 logger.info("Setting up tap interface under sudo") 1196 logger.info("Setting up tap interface under sudo")
1153 cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native) 1197 cmd = ('sudo', self.qemuifup, str(gid))
1154 try: 1198 try:
1155 tap = subprocess.check_output(cmd).decode('utf-8').strip() 1199 tap = subprocess.check_output(cmd).decode('utf-8').strip()
1156 except subprocess.CalledProcessError as e: 1200 except subprocess.CalledProcessError as e:
@@ -1166,7 +1210,7 @@ class BaseConfig(object):
1166 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") 1210 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
1167 sys.exit(1) 1211 sys.exit(1)
1168 self.tap = tap 1212 self.tap = tap
1169 tapnum = int(tap[3:]) 1213 tapnum = int(tap[len(oe_tap_name):])
1170 gateway = tapnum * 2 + 1 1214 gateway = tapnum * 2 + 1
1171 client = gateway + 1 1215 client = gateway + 1
1172 if self.fstype == 'nfs': 1216 if self.fstype == 'nfs':
@@ -1174,6 +1218,7 @@ class BaseConfig(object):
1174 netconf = " " + self.cmdline_ip_tap 1218 netconf = " " + self.cmdline_ip_tap
1175 netconf = netconf.replace('@CLIENT@', str(client)) 1219 netconf = netconf.replace('@CLIENT@', str(client))
1176 netconf = netconf.replace('@GATEWAY@', str(gateway)) 1220 netconf = netconf.replace('@GATEWAY@', str(gateway))
1221 self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway))
1177 logger.info("Network configuration:%s", netconf) 1222 logger.info("Network configuration:%s", netconf)
1178 self.kernel_cmdline_script += netconf 1223 self.kernel_cmdline_script += netconf
1179 mac = "%s%02x" % (self.mac_tap, client) 1224 mac = "%s%02x" % (self.mac_tap, client)
@@ -1189,7 +1234,8 @@ class BaseConfig(object):
1189 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) 1234 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
1190 1235
1191 def setup_network(self): 1236 def setup_network(self):
1192 if self.get('QB_NET') == 'none': 1237 if self.nonetwork or self.get('QB_NET') == 'none':
1238 self.set('NETWORK_CMD', '-nic none')
1193 return 1239 return
1194 if sys.stdin.isatty(): 1240 if sys.stdin.isatty():
1195 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() 1241 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip()
@@ -1210,6 +1256,18 @@ class BaseConfig(object):
1210 self.fstype = self.fstype[4:] 1256 self.fstype = self.fstype[4:]
1211 rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' 1257 rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw'
1212 1258
1259 tmpfsdir = os.environ.get("RUNQEMU_TMPFS_DIR", None)
1260 if self.snapshot and tmpfsdir:
1261 newrootfs = os.path.join(tmpfsdir, os.path.basename(self.rootfs)) + "." + str(os.getpid())
1262 logger.info("Copying rootfs to %s" % newrootfs)
1263 copy_start = time.time()
1264 shutil.copyfile(self.rootfs, newrootfs)
1265 logger.info("Copy done in %s seconds" % (time.time() - copy_start))
1266 self.rootfs = newrootfs
1267 # Don't need a second copy now!
1268 self.snapshot = False
1269 self.cleanup_files.append(newrootfs)
1270
1213 qb_rootfs_opt = self.get('QB_ROOTFS_OPT') 1271 qb_rootfs_opt = self.get('QB_ROOTFS_OPT')
1214 if qb_rootfs_opt: 1272 if qb_rootfs_opt:
1215 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) 1273 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs)
@@ -1254,7 +1312,13 @@ class BaseConfig(object):
1254 self.rootfs_options = vm_drive 1312 self.rootfs_options = vm_drive
1255 if not self.fstype in self.vmtypes: 1313 if not self.fstype in self.vmtypes:
1256 self.rootfs_options += ' -no-reboot' 1314 self.rootfs_options += ' -no-reboot'
1257 self.kernel_cmdline = 'root=%s rw' % (self.get('QB_KERNEL_ROOT')) 1315
1316 # By default, ' rw' is appended to QB_KERNEL_ROOT unless either ro or rw is explicitly passed.
1317 qb_kernel_root = self.get('QB_KERNEL_ROOT')
1318 qb_kernel_root_l = qb_kernel_root.split()
1319 if not ('ro' in qb_kernel_root_l or 'rw' in qb_kernel_root_l):
1320 qb_kernel_root += ' rw'
1321 self.kernel_cmdline = 'root=%s' % qb_kernel_root
1258 1322
1259 if self.fstype == 'nfs': 1323 if self.fstype == 'nfs':
1260 self.rootfs_options = '' 1324 self.rootfs_options = ''
@@ -1270,7 +1334,7 @@ class BaseConfig(object):
1270 """attempt to determine the appropriate qemu-system binary""" 1334 """attempt to determine the appropriate qemu-system binary"""
1271 mach = self.get('MACHINE') 1335 mach = self.get('MACHINE')
1272 if not mach: 1336 if not mach:
1273 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' 1337 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*'
1274 if self.rootfs: 1338 if self.rootfs:
1275 match = re.match(search, self.rootfs) 1339 match = re.match(search, self.rootfs)
1276 if match: 1340 if match:
@@ -1293,6 +1357,8 @@ class BaseConfig(object):
1293 qbsys = 'x86_64' 1357 qbsys = 'x86_64'
1294 elif mach == 'qemuppc': 1358 elif mach == 'qemuppc':
1295 qbsys = 'ppc' 1359 qbsys = 'ppc'
1360 elif mach == 'qemuloongarch64':
1361 qbsys = 'loongarch64'
1296 elif mach == 'qemumips': 1362 elif mach == 'qemumips':
1297 qbsys = 'mips' 1363 qbsys = 'mips'
1298 elif mach == 'qemumips64': 1364 elif mach == 'qemumips64':
@@ -1321,7 +1387,127 @@ class BaseConfig(object):
1321 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") 1387 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!")
1322 self.qemu_system = qemu_system 1388 self.qemu_system = qemu_system
1323 1389
1324 def setup_final(self): 1390 def check_render_nodes(self):
1391 render_hint = """If /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create one suitable for mesa llvmpipe software renderer."""
1392 try:
1393 content = os.listdir("/dev/dri")
1394 nodes = [i for i in content if i.startswith('renderD')]
1395 if len(nodes) == 0:
1396 raise RunQemuError("No render nodes found in /dev/dri/: %s. %s" %(content, render_hint))
1397 for n in nodes:
1398 try:
1399 with open(os.path.join("/dev/dri", n), "w") as f:
1400 f.close()
1401 break
1402 except IOError:
1403 pass
1404 else:
1405 raise RunQemuError("None of the render nodes in /dev/dri/ are accessible: %s; you may need to add yourself to 'render' group or otherwise ensure you have read-write permissions on one of them." %(nodes))
1406 except FileNotFoundError:
1407 raise RunQemuError("/dev/dri directory does not exist; no render nodes available on this machine. %s" %(render_hint))
1408
1409 def setup_guest_agent(self):
1410 if self.guest_agent == True:
1411 self.qemu_opt += ' -chardev socket,path=' + self.guest_agent_sockpath + ',server,nowait,id=qga0 '
1412 self.qemu_opt += ' -device virtio-serial '
1413 self.qemu_opt += ' -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 '
1414
1415 def setup_qmp(self):
1416 if self.qmp:
1417 self.qemu_opt += " -qmp %s,server,nowait" % self.qmp
1418
1419 def setup_vga(self):
1420 if self.nographic == True:
1421 if self.sdl == True:
1422 raise RunQemuError('Option nographic makes no sense alongside the sdl option.')
1423 if self.gtk == True:
1424 raise RunQemuError('Option nographic makes no sense alongside the gtk option.')
1425 self.qemu_opt += ' -nographic'
1426
1427 if self.novga == True:
1428 self.qemu_opt += ' -vga none'
1429 return
1430
1431 if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False):
1432 raise RunQemuError('Option gl/gl-es needs gtk or sdl option.')
1433
1434 # If we have no display option, we autodetect based upon what qemu supports. We
1435 # need our font setup and show-cusor below so we need to see what qemu --help says
1436 # is supported so we can pass our correct config in.
1437 if not self.nographic and not self.sdl and not self.gtk and not self.publicvnc and not self.egl_headless == True:
1438 output = subprocess.check_output([self.qemu_bin, "--help"], universal_newlines=True, env=self.qemu_environ)
1439 if "-display gtk" in output:
1440 self.gtk = True
1441 elif "-display sdl" in output:
1442 self.sdl = True
1443 else:
1444 self.qemu_opt += ' -display none'
1445
1446 if self.sdl == True or self.gtk == True or self.egl_headless == True:
1447
1448 if self.qemu_system.endswith(('i386', 'x86_64')):
1449 if self.gl or self.gl_es or self.egl_headless:
1450 self.qemu_opt += ' -device virtio-vga-gl '
1451 else:
1452 self.qemu_opt += ' -device virtio-vga '
1453
1454 self.qemu_opt += ' -display '
1455 if self.egl_headless == True:
1456 self.check_render_nodes()
1457 self.set_dri_path()
1458 self.qemu_opt += 'egl-headless,'
1459 else:
1460 if self.sdl == True:
1461 self.qemu_opt += 'sdl,'
1462 elif self.gtk == True:
1463 self.qemu_environ['FONTCONFIG_PATH'] = '/etc/fonts'
1464 self.qemu_opt += 'gtk,'
1465
1466 if self.gl == True:
1467 self.set_dri_path()
1468 self.qemu_opt += 'gl=on,'
1469 elif self.gl_es == True:
1470 self.set_dri_path()
1471 self.qemu_opt += 'gl=es,'
1472 self.qemu_opt += 'show-cursor=on'
1473
1474 self.qemu_opt += ' %s' %self.get('QB_GRAPHICS')
1475
1476 def setup_serial(self):
1477 # Setup correct kernel command line for serial
1478 if self.get('SERIAL_CONSOLES') and (self.serialstdio == True or self.serialconsole == True or self.nographic == True or self.tcpserial_portnum):
1479 for entry in self.get('SERIAL_CONSOLES').split(' '):
1480 self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1]
1481
1482 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES).
1483 # If no serial or serialtcp options were specified, only ttyS0 is created
1484 # and sysvinit shows an error trying to enable ttyS1:
1485 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1486 serial_num = len(re.findall("-serial", self.qemu_opt))
1487
1488 # Assume if the user passed serial options, they know what they want
1489 # and pad to two devices
1490 if serial_num == 1:
1491 self.qemu_opt += " -serial null"
1492 elif serial_num >= 2:
1493 return
1494
1495 if self.serialstdio == True or self.nographic == True:
1496 self.qemu_opt += " -serial mon:stdio"
1497 else:
1498 self.qemu_opt += " -serial mon:vc"
1499 if self.serialconsole:
1500 if sys.stdin.isatty():
1501 subprocess.check_call(("stty", "intr", "^]"))
1502 logger.info("Interrupt character is '^]'")
1503
1504 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1505
1506 serial_num = len(re.findall("-serial", self.qemu_opt))
1507 if serial_num < 2:
1508 self.qemu_opt += " -serial null"
1509
1510 def find_qemu(self):
1325 qemu_bin = os.path.join(self.bindir_native, self.qemu_system) 1511 qemu_bin = os.path.join(self.bindir_native, self.qemu_system)
1326 1512
1327 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't 1513 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't
@@ -1340,11 +1526,18 @@ class BaseConfig(object):
1340 1526
1341 if not os.access(qemu_bin, os.X_OK): 1527 if not os.access(qemu_bin, os.X_OK):
1342 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) 1528 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin)
1529 self.qemu_bin = qemu_bin
1343 1530
1344 self.qemu_opt = "%s %s %s %s %s" % (qemu_bin, self.get('NETWORK_CMD'), self.get('QB_RNG'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND')) 1531 def setup_final(self):
1532
1533 self.find_qemu()
1534
1535 self.qemu_opt = "%s %s %s %s %s" % (self.qemu_bin, self.get('NETWORK_CMD'), self.get('QB_RNG'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND').replace('@DEPLOY_DIR_IMAGE@', self.get('DEPLOY_DIR_IMAGE')))
1345 1536
1346 for ovmf in self.ovmf_bios: 1537 for ovmf in self.ovmf_bios:
1347 format = ovmf.rsplit('.', 1)[-1] 1538 format = ovmf.rsplit('.', 1)[-1]
1539 if format == "bin":
1540 format = "raw"
1348 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) 1541 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf)
1349 1542
1350 self.qemu_opt += ' ' + self.qemu_opt_script 1543 self.qemu_opt += ' ' + self.qemu_opt_script
@@ -1363,61 +1556,44 @@ class BaseConfig(object):
1363 if self.snapshot: 1556 if self.snapshot:
1364 self.qemu_opt += " -snapshot" 1557 self.qemu_opt += " -snapshot"
1365 1558
1366 if self.serialconsole: 1559 self.setup_guest_agent()
1367 if sys.stdin.isatty(): 1560 self.setup_qmp()
1368 subprocess.check_call(("stty", "intr", "^]")) 1561 self.setup_serial()
1369 logger.info("Interrupt character is '^]'") 1562 self.setup_vga()
1370
1371 first_serial = ""
1372 if not re.search("-nographic", self.qemu_opt):
1373 first_serial = "-serial mon:vc"
1374 # We always want a ttyS1. Since qemu by default adds a serial
1375 # port when nodefaults is not specified, it seems that all that
1376 # would be needed is to make sure a "-serial" is there. However,
1377 # it appears that when "-serial" is specified, it ignores the
1378 # default serial port that is normally added. So here we make
1379 # sure to add two -serial if there are none. And only one if
1380 # there is one -serial already.
1381 serial_num = len(re.findall("-serial", self.qemu_opt))
1382 if serial_num == 0:
1383 self.qemu_opt += " %s %s" % (first_serial, self.get("QB_SERIAL_OPT"))
1384 elif serial_num == 1:
1385 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1386
1387 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES),
1388 # if not serial or serialtcp options was specified only ttyS0 is created
1389 # and sysvinit shows an error trying to enable ttyS1:
1390 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1391 serial_num = len(re.findall("-serial", self.qemu_opt))
1392 if serial_num == 0:
1393 if re.search("-nographic", self.qemu_opt) or self.serialstdio:
1394 self.qemu_opt += " -serial mon:stdio -serial null"
1395 else:
1396 self.qemu_opt += " -serial mon:vc -serial null"
1397 1563
1398 def start_qemu(self): 1564 def start_qemu(self):
1399 import shlex 1565 import shlex
1400 if self.kernel: 1566 if self.kernel:
1401 kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline, 1567 kernel_opts = "-kernel %s" % (self.kernel)
1568 if self.get('QB_KERNEL_CMDLINE') == "none":
1569 if self.bootparams:
1570 kernel_opts += " -append '%s'" % (self.bootparams)
1571 else:
1572 kernel_opts += " -append '%s %s %s %s'" % (self.kernel_cmdline,
1402 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), 1573 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'),
1403 self.bootparams) 1574 self.bootparams)
1404 if self.bios:
1405 kernel_opts += " -bios %s" % self.bios
1406 if self.dtb: 1575 if self.dtb:
1407 kernel_opts += " -dtb %s" % self.dtb 1576 kernel_opts += " -dtb %s" % self.dtb
1408 else: 1577 else:
1409 kernel_opts = "" 1578 kernel_opts = ""
1579
1580 if self.bios:
1581 self.qemu_opt += " -bios %s" % self.bios
1582
1410 cmd = "%s %s" % (self.qemu_opt, kernel_opts) 1583 cmd = "%s %s" % (self.qemu_opt, kernel_opts)
1411 cmds = shlex.split(cmd) 1584 cmds = shlex.split(cmd)
1412 logger.info('Running %s\n' % cmd) 1585 logger.info('Running %s\n' % cmd)
1586 with open('/proc/uptime', 'r') as f:
1587 uptime_seconds = f.readline().split()[0]
1588 logger.info('Host uptime: %s\n' % uptime_seconds)
1413 pass_fds = [] 1589 pass_fds = []
1414 if self.taplock_descriptor: 1590 if self.taplock_descriptor:
1415 pass_fds = [self.taplock_descriptor.fileno()] 1591 pass_fds = [self.taplock_descriptor.fileno()]
1416 if len(self.portlocks): 1592 if len(self.portlocks):
1417 for descriptor in self.portlocks.values(): 1593 for descriptor in self.portlocks.values():
1418 pass_fds.append(descriptor.fileno()) 1594 pass_fds.append(descriptor.fileno())
1419 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds) 1595 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ)
1420 self.qemupid = process.pid 1596 self.qemuprocess = process
1421 retcode = process.wait() 1597 retcode = process.wait()
1422 if retcode: 1598 if retcode:
1423 if retcode == -signal.SIGTERM: 1599 if retcode == -signal.SIGTERM:
@@ -1425,6 +1601,13 @@ class BaseConfig(object):
1425 else: 1601 else:
1426 logger.error("Failed to run qemu: %s", process.stderr.read().decode()) 1602 logger.error("Failed to run qemu: %s", process.stderr.read().decode())
1427 1603
1604 def cleanup_cmd(self):
1605 cmd = self.get('QB_CLEANUP_CMD')
1606 if cmd != '':
1607 logger.info('Running cleanup command %s' % str(cmd))
1608 if subprocess.call(cmd, shell=True) != 0:
1609 raise RunQemuError('Failed to run %s' % str(cmd))
1610
1428 def cleanup(self): 1611 def cleanup(self):
1429 if self.cleaned: 1612 if self.cleaned:
1430 return 1613 return
@@ -1433,30 +1616,48 @@ class BaseConfig(object):
1433 signal.signal(signal.SIGTERM, signal.SIG_IGN) 1616 signal.signal(signal.SIGTERM, signal.SIG_IGN)
1434 1617
1435 logger.info("Cleaning up") 1618 logger.info("Cleaning up")
1619
1620 if self.qemuprocess:
1621 try:
1622 # give it some time to shut down, ignore return values and output
1623 self.qemuprocess.send_signal(signal.SIGTERM)
1624 self.qemuprocess.communicate(timeout=5)
1625 except subprocess.TimeoutExpired:
1626 self.qemuprocess.kill()
1627
1628 with open('/proc/uptime', 'r') as f:
1629 uptime_seconds = f.readline().split()[0]
1630 logger.info('Host uptime: %s\n' % uptime_seconds)
1436 if self.cleantap: 1631 if self.cleantap:
1437 cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native) 1632 cmd = ('sudo', self.qemuifdown, self.tap)
1438 logger.debug('Running %s' % str(cmd)) 1633 logger.debug('Running %s' % str(cmd))
1439 subprocess.check_call(cmd) 1634 subprocess.check_call(cmd)
1440 self.release_taplock() 1635 self.release_taplock()
1441 self.release_portlock()
1442 1636
1443 if self.nfs_running: 1637 if self.nfs_running:
1444 logger.info("Shutting down the userspace NFS server...") 1638 logger.info("Shutting down the userspace NFS server...")
1445 cmd = ("runqemu-export-rootfs", "stop", self.rootfs) 1639 cmd = ("runqemu-export-rootfs", "stop", self.rootfs)
1446 logger.debug('Running %s' % str(cmd)) 1640 logger.debug('Running %s' % str(cmd))
1447 subprocess.check_call(cmd) 1641 subprocess.check_call(cmd)
1642 self.release_portlock()
1448 1643
1449 if self.saved_stty: 1644 if self.saved_stty:
1450 subprocess.check_call(("stty", self.saved_stty)) 1645 subprocess.check_call(("stty", self.saved_stty))
1451 1646
1452 if self.clean_nfs_dir: 1647 if self.cleanup_files:
1453 logger.info('Removing %s' % self.rootfs) 1648 for ent in self.cleanup_files:
1454 shutil.rmtree(self.rootfs) 1649 logger.info('Removing %s' % ent)
1455 shutil.rmtree('%s.pseudo_state' % self.rootfs) 1650 if os.path.isfile(ent):
1651 os.remove(ent)
1652 else:
1653 shutil.rmtree(ent)
1654
1655 # Deliberately ignore the return code of 'tput smam'.
1656 subprocess.call(["tput", "smam"])
1456 1657
1457 self.cleaned = True 1658 self.cleaned = True
1458 1659
1459 def run_bitbake_env(self, mach=None): 1660 def run_bitbake_env(self, mach=None, target=''):
1460 bitbake = shutil.which('bitbake') 1661 bitbake = shutil.which('bitbake')
1461 if not bitbake: 1662 if not bitbake:
1462 return 1663 return
@@ -1469,22 +1670,33 @@ class BaseConfig(object):
1469 multiconfig = "mc:%s" % multiconfig 1670 multiconfig = "mc:%s" % multiconfig
1470 1671
1471 if mach: 1672 if mach:
1472 cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig) 1673 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1473 else: 1674 else:
1474 cmd = 'bitbake -e %s' % multiconfig 1675 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1475 1676
1476 logger.info('Running %s...' % cmd) 1677 logger.info('Running %s...' % cmd)
1477 return subprocess.check_output(cmd, shell=True).decode('utf-8') 1678 try:
1679 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1680 except subprocess.CalledProcessError as err:
1681 logger.warning("Couldn't run '%s' to gather environment information, maybe the target wasn't an image name, will retry with virtual/kernel as a target:\n%s" % (cmd, err.output.decode('utf-8')))
1682 # need something with IMAGE_NAME_SUFFIX/IMAGE_LINK_NAME defined (kernel also inherits image-artifact-names.bbclass)
1683 target = 'virtual/kernel'
1684 if mach:
1685 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1686 else:
1687 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1688 try:
1689 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1690 except subprocess.CalledProcessError as err:
1691 logger.warning("Couldn't run '%s' to gather environment information, giving up with 'bitbake -e':\n%s" % (cmd, err.output.decode('utf-8')))
1692 return ''
1693
1478 1694
1479 def load_bitbake_env(self, mach=None): 1695 def load_bitbake_env(self, mach=None, target=None):
1480 if self.bitbake_e: 1696 if self.bitbake_e:
1481 return 1697 return
1482 1698
1483 try: 1699 self.bitbake_e = self.run_bitbake_env(mach=mach, target=target)
1484 self.bitbake_e = self.run_bitbake_env(mach=mach)
1485 except subprocess.CalledProcessError as err:
1486 self.bitbake_e = ''
1487 logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8'))
1488 1700
1489 def validate_combos(self): 1701 def validate_combos(self):
1490 if (self.fstype in self.vmtypes) and self.kernel: 1702 if (self.fstype in self.vmtypes) and self.kernel:
@@ -1514,7 +1726,7 @@ class BaseConfig(object):
1514 return result 1726 return result
1515 raise RunQemuError("Native sysroot directory %s doesn't exist" % result) 1727 raise RunQemuError("Native sysroot directory %s doesn't exist" % result)
1516 else: 1728 else:
1517 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd) 1729 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd))
1518 1730
1519 1731
1520def main(): 1732def main():
@@ -1530,11 +1742,8 @@ def main():
1530 subprocess.check_call([renice, str(os.getpid())]) 1742 subprocess.check_call([renice, str(os.getpid())])
1531 1743
1532 def sigterm_handler(signum, frame): 1744 def sigterm_handler(signum, frame):
1533 logger.info("SIGTERM received") 1745 logger.info("Received signal: %s" % (signum))
1534 os.kill(config.qemupid, signal.SIGTERM)
1535 config.cleanup() 1746 config.cleanup()
1536 # Deliberately ignore the return code of 'tput smam'.
1537 subprocess.call(["tput", "smam"])
1538 signal.signal(signal.SIGTERM, sigterm_handler) 1747 signal.signal(signal.SIGTERM, sigterm_handler)
1539 1748
1540 config.check_args() 1749 config.check_args()
@@ -1546,6 +1755,7 @@ def main():
1546 config.setup_network() 1755 config.setup_network()
1547 config.setup_rootfs() 1756 config.setup_rootfs()
1548 config.setup_final() 1757 config.setup_final()
1758 config.setup_cmd()
1549 config.start_qemu() 1759 config.start_qemu()
1550 except RunQemuError as err: 1760 except RunQemuError as err:
1551 logger.error(err) 1761 logger.error(err)
@@ -1555,9 +1765,8 @@ def main():
1555 traceback.print_exc() 1765 traceback.print_exc()
1556 return 1 1766 return 1
1557 finally: 1767 finally:
1768 config.cleanup_cmd()
1558 config.cleanup() 1769 config.cleanup()
1559 # Deliberately ignore the return code of 'tput smam'.
1560 subprocess.call(["tput", "smam"])
1561 1770
1562if __name__ == "__main__": 1771if __name__ == "__main__":
1563 sys.exit(main()) 1772 sys.exit(main())