1#!/usr/bin/env python3 2 3# Handle running OE images standalone with QEMU 4# 5# Copyright (C) 2006-2011 Linux Foundation 6# Copyright (c) 2016 Wind River Systems, Inc. 7# 8# SPDX-License-Identifier: GPL-2.0-only 9# 10 11import os 12import sys 13import logging 14import subprocess 15import re 16import fcntl 17import shutil 18import glob 19import configparser 20import signal 21import time 22 23class RunQemuError(Exception): 24 """Custom exception to raise on known errors.""" 25 pass 26 27class OEPathError(RunQemuError): 28 """Custom Exception to give better guidance on missing binaries""" 29 def __init__(self, message): 30 super().__init__("In order for this script to dynamically infer paths\n \ 31kernels or filesystem images, you either need bitbake in your PATH\n \ 32or to source oe-init-build-env before running this script.\n\n \ 33Dynamic path inference can be avoided by passing a *.qemuboot.conf to\n \ 34runqemu, i.e. `runqemu /path/to/my-image-name.qemuboot.conf`\n\n %s" % message) 35 36 37def create_logger(): 38 logger = logging.getLogger('runqemu') 39 logger.setLevel(logging.INFO) 40 41 # create console handler and set level to debug 42 ch = logging.StreamHandler() 43 ch.setLevel(logging.DEBUG) 44 45 # create formatter 46 formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') 47 48 # add formatter to ch 49 ch.setFormatter(formatter) 50 51 # add ch to logger 52 logger.addHandler(ch) 53 54 return logger 55 56logger = create_logger() 57 58def print_usage(): 59 print(""" 60Usage: you can run this script with any valid combination 61of the following environment variables (in any order): 62 KERNEL - the kernel image file to use 63 BIOS - the bios image file to use 64 ROOTFS - the rootfs image file or nfsroot directory to use 65 DEVICE_TREE - the device tree blob to use 66 MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) 67 Simplified QEMU command-line options can be passed with: 68 nographic - disable video console 69 novga - Disable VGA emulation completely 70 sdl - choose the SDL UI frontend 71 gtk - choose the Gtk UI frontend 72 gl - enable virgl-based GL acceleration (also needs gtk or sdl options) 73 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) 74 egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it 75 (hint: if /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create 76 one suitable for mesa llvmpipe software renderer) 77 serial - enable a serial console on /dev/ttyS0 78 serialstdio - enable a serial console on the console (regardless of graphics mode) 79 slirp - enable user networking, no root privilege is required 80 snapshot - don't write changes back to images 81 kvm - enable KVM when running x86/x86_64 (VT-capable CPU required) 82 kvm-vhost - enable KVM with vhost when running x86/x86_64 (VT-capable CPU required) 83 publicvnc - enable a VNC server open to all hosts 84 audio - enable audio 85 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI 86 tcpserial=<port> - specify tcp serial port number 87 qemuparams=<xyz> - specify custom parameters to QEMU 88 bootparams=<xyz> - specify custom kernel parameters during boot 89 help, -h, --help: print this text 90 -d, --debug: Enable debug output 91 -q, --quiet: Hide most output except error messages 92 93Examples: 94 runqemu 95 runqemu qemuarm 96 runqemu tmp/deploy/images/qemuarm 97 runqemu tmp/deploy/images/qemux86/<qemuboot.conf> 98 runqemu qemux86-64 core-image-sato ext4 99 runqemu qemux86-64 wic-image-minimal wic 100 runqemu path/to/bzImage-qemux86.bin path/to/nfsrootdir/ serial 101 runqemu qemux86 iso/hddimg/wic.vmdk/wic.vhd/wic.vhdx/wic.qcow2/wic.vdi/ramfs/cpio.gz... 102 runqemu qemux86 qemuparams="-m 256" 103 runqemu qemux86 bootparams="psplash=false" 104 runqemu path/to/<image>-<machine>.wic 105 runqemu path/to/<image>-<machine>.wic.vmdk 106 runqemu path/to/<image>-<machine>.wic.vhdx 107 runqemu path/to/<image>-<machine>.wic.vhd 108""") 109 110def check_tun(): 111 """Check /dev/net/tun""" 112 dev_tun = '/dev/net/tun' 113 if not os.path.exists(dev_tun): 114 raise RunQemuError("TUN control device %s is unavailable; you may need to enable TUN (e.g. sudo modprobe tun)" % dev_tun) 115 116 if not os.access(dev_tun, os.W_OK): 117 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) 118 119def get_first_file(cmds): 120 """Return first file found in wildcard cmds""" 121 for cmd in cmds: 122 all_files = glob.glob(cmd) 123 if all_files: 124 for f in all_files: 125 if not os.path.isdir(f): 126 return f 127 return '' 128 129class BaseConfig(object): 130 def __init__(self): 131 # The self.d saved vars from self.set(), part of them are from qemuboot.conf 132 self.d = {'QB_KERNEL_ROOT': '/dev/vda'} 133 134 # Supported env vars, add it here if a var can be got from env, 135 # and don't use os.getenv in the code. 136 self.env_vars = ('MACHINE', 137 'ROOTFS', 138 'KERNEL', 139 'BIOS', 140 'DEVICE_TREE', 141 'DEPLOY_DIR_IMAGE', 142 'OE_TMPDIR', 143 'OECORE_NATIVE_SYSROOT', 144 'MULTICONFIG', 145 'SERIAL_CONSOLES', 146 ) 147 148 self.qemu_opt = '' 149 self.qemu_opt_script = '' 150 self.qemuparams = '' 151 self.nfs_server = '' 152 self.rootfs = '' 153 # File name(s) of a OVMF firmware file or variable store, 154 # to be added with -drive if=pflash. 155 # Found in the same places as the rootfs, with or without one of 156 # these suffices: qcow2, bin. 157 self.ovmf_bios = [] 158 # When enrolling default Secure Boot keys, the hypervisor 159 # must provide the Platform Key and the first Key Exchange Key 160 # certificate in the Type 11 SMBIOS table. 161 self.ovmf_secboot_pkkek1 = '' 162 self.qemuboot = '' 163 self.qbconfload = False 164 self.kernel = '' 165 self.bios = '' 166 self.kernel_cmdline = '' 167 self.kernel_cmdline_script = '' 168 self.bootparams = '' 169 self.dtb = '' 170 self.fstype = '' 171 self.kvm_enabled = False 172 self.vhost_enabled = False 173 self.slirp_enabled = False 174 self.net_bridge = None 175 self.nfs_instance = 0 176 self.nfs_running = False 177 self.serialconsole = False 178 self.serialstdio = False 179 self.nographic = False 180 self.sdl = False 181 self.gtk = False 182 self.gl = False 183 self.gl_es = False 184 self.egl_headless = False 185 self.publicvnc = False 186 self.novga = False 187 self.cleantap = False 188 self.saved_stty = '' 189 self.audio_enabled = False 190 self.tcpserial_portnum = '' 191 self.taplock = '' 192 self.taplock_descriptor = None 193 self.portlocks = {} 194 self.bitbake_e = '' 195 self.snapshot = False 196 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") 197 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 198 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz') 199 self.vmtypes = ('hddimg', 'iso') 200 self.fsinfo = {} 201 self.network_device = "-device e1000,netdev=net0,mac=@MAC@" 202 self.cmdline_ip_slirp = "ip=dhcp" 203 self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0::eth0:off:8.8.8.8" 204 # Use different mac section for tap and slirp to avoid 205 # conflicts, e.g., when one is running with tap, the other is 206 # running with slirp. 207 # The last section is dynamic, which is for avoiding conflicts, 208 # when multiple qemus are running, e.g., when multiple tap or 209 # slirp qemus are running. 210 self.mac_tap = "52:54:00:12:34:" 211 self.mac_slirp = "52:54:00:12:35:" 212 # pid of the actual qemu process 213 self.qemu_environ = os.environ.copy() 214 self.qemuprocess = None 215 # avoid cleanup twice 216 self.cleaned = False 217 # Files to cleanup after run 218 self.cleanup_files = [] 219 220 def acquire_taplock(self, error=True): 221 logger.debug("Acquiring lockfile %s..." % self.taplock) 222 try: 223 self.taplock_descriptor = open(self.taplock, 'w') 224 fcntl.flock(self.taplock_descriptor, fcntl.LOCK_EX|fcntl.LOCK_NB) 225 except Exception as e: 226 msg = "Acquiring lockfile %s failed: %s" % (self.taplock, e) 227 if error: 228 logger.error(msg) 229 else: 230 logger.info(msg) 231 if self.taplock_descriptor: 232 self.taplock_descriptor.close() 233 self.taplock_descriptor = None 234 return False 235 return True 236 237 def release_taplock(self): 238 if self.taplock_descriptor: 239 logger.debug("Releasing lockfile for tap device '%s'" % self.tap) 240 # We pass the fd to the qemu process and if we unlock here, it would unlock for 241 # that too. Therefore don't unlock, just close 242 # fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN) 243 self.taplock_descriptor.close() 244 # Removing the file is a potential race, don't do that either 245 # os.remove(self.taplock) 246 self.taplock_descriptor = None 247 248 def check_free_port(self, host, port, lockdir): 249 """ Check whether the port is free or not """ 250 import socket 251 from contextlib import closing 252 253 lockfile = os.path.join(lockdir, str(port) + '.lock') 254 if self.acquire_portlock(lockfile): 255 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: 256 if sock.connect_ex((host, port)) == 0: 257 # Port is open, so not free 258 self.release_portlock(lockfile) 259 return False 260 else: 261 # Port is not open, so free 262 return True 263 else: 264 return False 265 266 def acquire_portlock(self, lockfile): 267 logger.debug("Acquiring lockfile %s..." % lockfile) 268 try: 269 portlock_descriptor = open(lockfile, 'w') 270 self.portlocks.update({lockfile: portlock_descriptor}) 271 fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_EX|fcntl.LOCK_NB) 272 except Exception as e: 273 msg = "Acquiring lockfile %s failed: %s" % (lockfile, e) 274 logger.info(msg) 275 if lockfile in self.portlocks.keys() and self.portlocks[lockfile]: 276 self.portlocks[lockfile].close() 277 del self.portlocks[lockfile] 278 return False 279 return True 280 281 def release_portlock(self, lockfile=None): 282 if lockfile != None: 283 logger.debug("Releasing lockfile '%s'" % lockfile) 284 # We pass the fd to the qemu process and if we unlock here, it would unlock for 285 # that too. Therefore don't unlock, just close 286 # fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) 287 self.portlocks[lockfile].close() 288 # Removing the file is a potential race, don't do that either 289 # os.remove(lockfile) 290 del self.portlocks[lockfile] 291 elif len(self.portlocks): 292 for lockfile, descriptor in self.portlocks.items(): 293 logger.debug("Releasing lockfile '%s'" % lockfile) 294 # We pass the fd to the qemu process and if we unlock here, it would unlock for 295 # that too. Therefore don't unlock, just close 296 # fcntl.flock(descriptor, fcntl.LOCK_UN) 297 descriptor.close() 298 # Removing the file is a potential race, don't do that either 299 # os.remove(lockfile) 300 self.portlocks = {} 301 302 def get(self, key): 303 if key in self.d: 304 return self.d.get(key) 305 elif os.getenv(key): 306 return os.getenv(key) 307 else: 308 return '' 309 310 def set(self, key, value): 311 self.d[key] = value 312 313 def is_deploy_dir_image(self, p): 314 if os.path.isdir(p): 315 if not re.search('.qemuboot.conf$', '\n'.join(os.listdir(p)), re.M): 316 logger.debug("Can't find required *.qemuboot.conf in %s" % p) 317 return False 318 if not any(map(lambda name: '-image-' in name, os.listdir(p))): 319 logger.debug("Can't find *-image-* in %s" % p) 320 return False 321 return True 322 else: 323 return False 324 325 def check_arg_fstype(self, fst): 326 """Check and set FSTYPE""" 327 if fst not in self.fstypes + self.vmtypes + self.wictypes: 328 logger.warning("Maybe unsupported FSTYPE: %s" % fst) 329 if not self.fstype or self.fstype == fst: 330 if fst == 'ramfs': 331 fst = 'cpio.gz' 332 if fst in ('tar.bz2', 'tar.gz'): 333 fst = 'nfs' 334 self.fstype = fst 335 else: 336 raise RunQemuError("Conflicting: FSTYPE %s and %s" % (self.fstype, fst)) 337 338 def set_machine_deploy_dir(self, machine, deploy_dir_image): 339 """Set MACHINE and DEPLOY_DIR_IMAGE""" 340 logger.debug('MACHINE: %s' % machine) 341 self.set("MACHINE", machine) 342 logger.debug('DEPLOY_DIR_IMAGE: %s' % deploy_dir_image) 343 self.set("DEPLOY_DIR_IMAGE", deploy_dir_image) 344 345 def check_arg_nfs(self, p): 346 if os.path.isdir(p): 347 self.rootfs = p 348 else: 349 m = re.match('(.*):(.*)', p) 350 self.nfs_server = m.group(1) 351 self.rootfs = m.group(2) 352 self.check_arg_fstype('nfs') 353 354 def check_arg_path(self, p): 355 """ 356 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf 357 - Check whether it is a kernel file 358 - Check whether it is an image file 359 - Check whether it is an NFS dir 360 - Check whether it is an OVMF flash file 361 """ 362 if p.endswith('.qemuboot.conf'): 363 self.qemuboot = p 364 self.qbconfload = True 365 elif re.search('\.bin$', p) or re.search('bzImage', p) or \ 366 re.search('zImage', p) or re.search('vmlinux', p) or \ 367 re.search('fitImage', p) or re.search('uImage', p): 368 self.kernel = p 369 elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p): 370 self.rootfs = p 371 # Check filename against self.fstypes can handle <file>.cpio.gz, 372 # otherwise, its type would be "gz", which is incorrect. 373 fst = "" 374 for t in self.fstypes: 375 if p.endswith(t): 376 fst = t 377 break 378 if not fst: 379 m = re.search('.*\.(.*)$', self.rootfs) 380 if m: 381 fst = m.group(1) 382 if fst: 383 self.check_arg_fstype(fst) 384 qb = re.sub('\.' + fst + "$", '', self.rootfs) 385 qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf') 386 if os.path.exists(qb): 387 self.qemuboot = qb 388 self.qbconfload = True 389 else: 390 logger.warning("%s doesn't exist" % qb) 391 else: 392 raise RunQemuError("Can't find FSTYPE from: %s" % p) 393 394 elif os.path.isdir(p) or re.search(':', p) and re.search('/', p): 395 if self.is_deploy_dir_image(p): 396 logger.debug('DEPLOY_DIR_IMAGE: %s' % p) 397 self.set("DEPLOY_DIR_IMAGE", p) 398 else: 399 logger.debug("Assuming %s is an nfs rootfs" % p) 400 self.check_arg_nfs(p) 401 elif os.path.basename(p).startswith('ovmf'): 402 self.ovmf_bios.append(p) 403 else: 404 raise RunQemuError("Unknown path arg %s" % p) 405 406 def check_arg_machine(self, arg): 407 """Check whether it is a machine""" 408 if self.get('MACHINE') == arg: 409 return 410 elif self.get('MACHINE') and self.get('MACHINE') != arg: 411 raise RunQemuError("Maybe conflicted MACHINE: %s vs %s" % (self.get('MACHINE'), arg)) 412 elif re.search('/', arg): 413 raise RunQemuError("Unknown arg: %s" % arg) 414 415 logger.debug('Assuming MACHINE = %s' % arg) 416 417 # if we're running under testimage, or similarly as a child 418 # of an existing bitbake invocation, we can't invoke bitbake 419 # to validate the MACHINE setting and must assume it's correct... 420 # FIXME: testimage.bbclass exports these two variables into env, 421 # are there other scenarios in which we need to support being 422 # invoked by bitbake? 423 deploy = self.get('DEPLOY_DIR_IMAGE') 424 bbchild = deploy and self.get('OE_TMPDIR') 425 if bbchild: 426 self.set_machine_deploy_dir(arg, deploy) 427 return 428 # also check whether we're running under a sourced toolchain 429 # environment file 430 if self.get('OECORE_NATIVE_SYSROOT'): 431 self.set("MACHINE", arg) 432 return 433 434 self.bitbake_e = self.run_bitbake_env(arg) 435 # bitbake -e doesn't report invalid MACHINE as an error, so 436 # let's check DEPLOY_DIR_IMAGE to make sure that it is a valid 437 # MACHINE. 438 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 439 if s: 440 deploy_dir_image = s.group(1) 441 else: 442 raise RunQemuError("bitbake -e %s" % self.bitbake_e) 443 if self.is_deploy_dir_image(deploy_dir_image): 444 self.set_machine_deploy_dir(arg, deploy_dir_image) 445 else: 446 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) 447 self.set("MACHINE", arg) 448 449 def set_dri_path(self): 450 # As runqemu can be run within bitbake (when using testimage, for example), 451 # we need to ensure that we run host pkg-config, and that it does not 452 # get mis-directed to native build paths set by bitbake. 453 env = os.environ.copy() 454 try: 455 del env['PKG_CONFIG_PATH'] 456 del env['PKG_CONFIG_DIR'] 457 del env['PKG_CONFIG_LIBDIR'] 458 del env['PKG_CONFIG_SYSROOT_DIR'] 459 except KeyError: 460 pass 461 try: 462 dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True, env=env) 463 except subprocess.CalledProcessError as e: 464 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.") 465 self.qemu_environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip() 466 467 # This preloads uninative libc pieces and therefore ensures that RPATH/RUNPATH 468 # in host mesa drivers doesn't trick uninative into loading host libc. 469 preload_items = ['libdl.so.2', 'librt.so.1', 'libpthread.so.0'] 470 uninative_path = os.path.dirname(self.get("UNINATIVE_LOADER")) 471 if os.path.exists(uninative_path): 472 preload_paths = [os.path.join(uninative_path, i) for i in preload_items] 473 self.qemu_environ['LD_PRELOAD'] = " ".join(preload_paths) 474 475 def check_args(self): 476 for debug in ("-d", "--debug"): 477 if debug in sys.argv: 478 logger.setLevel(logging.DEBUG) 479 sys.argv.remove(debug) 480 481 for quiet in ("-q", "--quiet"): 482 if quiet in sys.argv: 483 logger.setLevel(logging.ERROR) 484 sys.argv.remove(quiet) 485 486 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: 487 self.qemu_environ['SDL_RENDER_DRIVER'] = 'software' 488 self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false' 489 490 unknown_arg = "" 491 for arg in sys.argv[1:]: 492 if arg in self.fstypes + self.vmtypes + self.wictypes: 493 self.check_arg_fstype(arg) 494 elif arg == 'nographic': 495 self.nographic = True 496 elif arg == 'sdl': 497 self.sdl = True 498 elif arg == 'gtk': 499 self.gtk = True 500 elif arg == 'gl': 501 self.gl = True 502 elif arg == 'gl-es': 503 self.gl_es = True 504 elif arg == 'egl-headless': 505 self.egl_headless = True 506 elif arg == 'novga': 507 self.novga = True 508 elif arg == 'serial': 509 self.serialconsole = True 510 elif arg == "serialstdio": 511 self.serialstdio = True 512 elif arg == 'audio': 513 logger.info("Enabling audio in qemu") 514 logger.info("Please install sound drivers in linux host") 515 self.audio_enabled = True 516 elif arg == 'kvm': 517 self.kvm_enabled = True 518 elif arg == 'kvm-vhost': 519 self.vhost_enabled = True 520 elif arg == 'slirp': 521 self.slirp_enabled = True 522 elif arg.startswith('bridge='): 523 self.net_bridge = '%s' % arg[len('bridge='):] 524 elif arg == 'snapshot': 525 self.snapshot = True 526 elif arg == 'publicvnc': 527 self.publicvnc = True 528 self.qemu_opt_script += ' -vnc :0' 529 elif arg.startswith('tcpserial='): 530 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] 531 elif arg.startswith('qemuparams='): 532 self.qemuparams = ' %s' % arg[len('qemuparams='):] 533 elif arg.startswith('bootparams='): 534 self.bootparams = arg[len('bootparams='):] 535 elif os.path.exists(arg) or (re.search(':', arg) and re.search('/', arg)): 536 self.check_arg_path(os.path.abspath(arg)) 537 elif re.search(r'-image-|-image$', arg): 538 # Lazy rootfs 539 self.rootfs = arg 540 elif arg.startswith('ovmf'): 541 self.ovmf_bios.append(arg) 542 else: 543 # At last, assume it is the MACHINE 544 if (not unknown_arg) or unknown_arg == arg: 545 unknown_arg = arg 546 else: 547 raise RunQemuError("Can't handle two unknown args: %s %s\n" 548 "Try 'runqemu help' on how to use it" % \ 549 (unknown_arg, arg)) 550 # Check to make sure it is a valid machine 551 if unknown_arg and self.get('MACHINE') != unknown_arg: 552 if self.get('DEPLOY_DIR_IMAGE'): 553 machine = os.path.basename(self.get('DEPLOY_DIR_IMAGE')) 554 if unknown_arg == machine: 555 self.set("MACHINE", machine) 556 557 self.check_arg_machine(unknown_arg) 558 559 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): 560 self.load_bitbake_env() 561 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 562 if s: 563 self.set("DEPLOY_DIR_IMAGE", s.group(1)) 564 565 def check_kvm(self): 566 """Check kvm and kvm-host""" 567 if not (self.kvm_enabled or self.vhost_enabled): 568 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP')) 569 return 570 571 if not self.get('QB_CPU_KVM'): 572 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") 573 574 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP')) 575 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" 576 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" 577 dev_kvm = '/dev/kvm' 578 dev_vhost = '/dev/vhost-net' 579 if self.qemu_system.endswith(('i386', 'x86_64')): 580 with open('/proc/cpuinfo', 'r') as f: 581 kvm_cap = re.search('vmx|svm', "".join(f.readlines())) 582 if not kvm_cap: 583 logger.error("You are trying to enable KVM on a cpu without VT support.") 584 logger.error("Remove kvm from the command-line, or refer:") 585 raise RunQemuError(yocto_kvm_wiki) 586 587 if not os.path.exists(dev_kvm): 588 logger.error("Missing KVM device. Have you inserted kvm modules?") 589 logger.error("For further help see:") 590 raise RunQemuError(yocto_kvm_wiki) 591 592 if os.access(dev_kvm, os.W_OK|os.R_OK): 593 self.qemu_opt_script += ' -enable-kvm' 594 if self.get('MACHINE') == "qemux86": 595 # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs 596 # See YOCTO #12301 597 # On 64 bit we use x2apic 598 self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic" 599 else: 600 logger.error("You have no read or write permission on /dev/kvm.") 601 logger.error("Please change the ownership of this file as described at:") 602 raise RunQemuError(yocto_kvm_wiki) 603 604 if self.vhost_enabled: 605 if not os.path.exists(dev_vhost): 606 logger.error("Missing virtio net device. Have you inserted vhost-net module?") 607 logger.error("For further help see:") 608 raise RunQemuError(yocto_paravirt_kvm_wiki) 609 610 if not os.access(dev_vhost, os.W_OK|os.R_OK): 611 logger.error("You have no read or write permission on /dev/vhost-net.") 612 logger.error("Please change the ownership of this file as described at:") 613 raise RunQemuError(yocto_paravirt_kvm_wiki) 614 615 def check_fstype(self): 616 """Check and setup FSTYPE""" 617 if not self.fstype: 618 fstype = self.get('QB_DEFAULT_FSTYPE') 619 if fstype: 620 self.fstype = fstype 621 else: 622 raise RunQemuError("FSTYPE is NULL!") 623 624 # parse QB_FSINFO into dict, e.g. { 'wic': ['no-kernel-in-fs', 'a-flag'], 'ext4': ['another-flag']} 625 wic_fs = False 626 qb_fsinfo = self.get('QB_FSINFO') 627 if qb_fsinfo: 628 qb_fsinfo = qb_fsinfo.split() 629 for fsinfo in qb_fsinfo: 630 try: 631 fstype, fsflag = fsinfo.split(':') 632 633 if fstype == 'wic': 634 if fsflag == 'no-kernel-in-fs': 635 wic_fs = True 636 elif fsflag == 'kernel-in-fs': 637 wic_fs = False 638 else: 639 logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) 640 continue 641 else: 642 logger.warn('QB_FSINFO is not supported for image type "%s"', fstype) 643 continue 644 645 if fstype in self.fsinfo: 646 self.fsinfo[fstype].append(fsflag) 647 else: 648 self.fsinfo[fstype] = [fsflag] 649 except Exception: 650 logger.error('Invalid parameter "%s" in QB_FSINFO', fsinfo) 651 652 # treat wic images as vmimages (with kernel) or as fsimages (rootfs only) 653 if wic_fs: 654 self.fstypes = self.fstypes + self.wictypes 655 else: 656 self.vmtypes = self.vmtypes + self.wictypes 657 658 def check_rootfs(self): 659 """Check and set rootfs""" 660 661 if self.fstype == "none": 662 return 663 664 if self.get('ROOTFS'): 665 if not self.rootfs: 666 self.rootfs = self.get('ROOTFS') 667 elif self.get('ROOTFS') != self.rootfs: 668 raise RunQemuError("Maybe conflicted ROOTFS: %s vs %s" % (self.get('ROOTFS'), self.rootfs)) 669 670 if self.fstype == 'nfs': 671 return 672 673 if self.rootfs and not os.path.exists(self.rootfs): 674 # Lazy rootfs 675 self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), 676 self.rootfs, self.get('MACHINE'), 677 self.fstype) 678 elif not self.rootfs: 679 cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) 680 cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) 681 cmds = (cmd_name, cmd_link) 682 self.rootfs = get_first_file(cmds) 683 if not self.rootfs: 684 raise RunQemuError("Failed to find rootfs: %s or %s" % cmds) 685 686 if not os.path.exists(self.rootfs): 687 raise RunQemuError("Can't find rootfs: %s" % self.rootfs) 688 689 def setup_pkkek1(self): 690 """ 691 Extract from PEM certificate the Platform Key and first Key 692 Exchange Key certificate string. The hypervisor needs to provide 693 it in the Type 11 SMBIOS table 694 """ 695 pemcert = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), 'OvmfPkKek1.pem') 696 try: 697 with open(pemcert, 'r') as pemfile: 698 key = pemfile.read().replace('\n', ''). \ 699 replace('-----BEGIN CERTIFICATE-----', ''). \ 700 replace('-----END CERTIFICATE-----', '') 701 self.ovmf_secboot_pkkek1 = key 702 703 except FileNotFoundError: 704 raise RunQemuError("Can't open PEM certificate %s " % pemcert) 705 706 def check_ovmf(self): 707 """Check and set full path for OVMF firmware and variable file(s).""" 708 709 for index, ovmf in enumerate(self.ovmf_bios): 710 if os.path.exists(ovmf): 711 continue 712 for suffix in ('qcow2', 'bin'): 713 path = '%s/%s.%s' % (self.get('DEPLOY_DIR_IMAGE'), ovmf, suffix) 714 if os.path.exists(path): 715 self.ovmf_bios[index] = path 716 if ovmf.endswith('secboot'): 717 self.setup_pkkek1() 718 break 719 else: 720 raise RunQemuError("Can't find OVMF firmware: %s" % ovmf) 721 722 def check_kernel(self): 723 """Check and set kernel""" 724 # The vm image doesn't need a kernel 725 if self.fstype in self.vmtypes: 726 return 727 728 # See if the user supplied a KERNEL option 729 if self.get('KERNEL'): 730 self.kernel = self.get('KERNEL') 731 732 # QB_DEFAULT_KERNEL is always a full file path 733 kernel_name = os.path.basename(self.get('QB_DEFAULT_KERNEL')) 734 735 # The user didn't want a kernel to be loaded 736 if kernel_name == "none" and not self.kernel: 737 return 738 739 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 740 if not self.kernel: 741 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) 742 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 743 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 744 cmds = (kernel_match_name, kernel_match_link, kernel_startswith) 745 self.kernel = get_first_file(cmds) 746 if not self.kernel: 747 raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds) 748 749 if not os.path.exists(self.kernel): 750 raise RunQemuError("KERNEL %s not found" % self.kernel) 751 752 def check_dtb(self): 753 """Check and set dtb""" 754 # Did the user specify a device tree? 755 if self.get('DEVICE_TREE'): 756 self.dtb = self.get('DEVICE_TREE') 757 if not os.path.exists(self.dtb): 758 raise RunQemuError('Specified DTB not found: %s' % self.dtb) 759 return 760 761 dtb = self.get('QB_DTB') 762 if dtb: 763 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 764 cmd_match = "%s/%s" % (deploy_dir_image, dtb) 765 cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb) 766 cmd_wild = "%s/*.dtb" % deploy_dir_image 767 cmds = (cmd_match, cmd_startswith, cmd_wild) 768 self.dtb = get_first_file(cmds) 769 if not os.path.exists(self.dtb): 770 raise RunQemuError('DTB not found: %s, %s or %s' % cmds) 771 772 def check_bios(self): 773 """Check and set bios""" 774 775 # See if the user supplied a BIOS option 776 if self.get('BIOS'): 777 self.bios = self.get('BIOS') 778 779 # QB_DEFAULT_BIOS is always a full file path 780 bios_name = os.path.basename(self.get('QB_DEFAULT_BIOS')) 781 782 # The user didn't want a bios to be loaded 783 if (bios_name == "" or bios_name == "none") and not self.bios: 784 return 785 786 if not self.bios: 787 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 788 self.bios = "%s/%s" % (deploy_dir_image, bios_name) 789 790 if not self.bios: 791 raise RunQemuError('BIOS not found: %s' % bios_match_name) 792 793 if not os.path.exists(self.bios): 794 raise RunQemuError("BIOS %s not found" % self.bios) 795 796 797 def check_mem(self): 798 """ 799 Both qemu and kernel needs memory settings, so check QB_MEM and set it 800 for both. 801 """ 802 s = re.search('-m +([0-9]+)', self.qemuparams) 803 if s: 804 self.set('QB_MEM', '-m %s' % s.group(1)) 805 elif not self.get('QB_MEM'): 806 logger.info('QB_MEM is not set, use 256M by default') 807 self.set('QB_MEM', '-m 256') 808 809 # Check and remove M or m suffix 810 qb_mem = self.get('QB_MEM') 811 if qb_mem.endswith('M') or qb_mem.endswith('m'): 812 qb_mem = qb_mem[:-1] 813 814 # Add -m prefix it not present 815 if not qb_mem.startswith('-m'): 816 qb_mem = '-m %s' % qb_mem 817 818 self.set('QB_MEM', qb_mem) 819 820 mach = self.get('MACHINE') 821 if not mach.startswith(('qemumips', 'qemux86')): 822 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' 823 824 self.qemu_opt_script += ' %s' % self.get('QB_MEM') 825 826 def check_tcpserial(self): 827 if self.tcpserial_portnum: 828 ports = self.tcpserial_portnum.split(':') 829 port = ports[0] 830 if self.get('QB_TCPSERIAL_OPT'): 831 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) 832 else: 833 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 834 835 if len(ports) > 1: 836 for port in ports[1:]: 837 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port 838 839 def check_and_set(self): 840 """Check configs sanity and set when needed""" 841 self.validate_paths() 842 if not self.slirp_enabled and not self.net_bridge: 843 check_tun() 844 # Check audio 845 if self.audio_enabled: 846 if not self.get('QB_AUDIO_DRV'): 847 raise RunQemuError("QB_AUDIO_DRV is NULL, this board doesn't support audio") 848 if not self.get('QB_AUDIO_OPT'): 849 logger.warning('QB_AUDIO_OPT is NULL, you may need define it to make audio work') 850 else: 851 self.qemu_opt_script += ' %s' % self.get('QB_AUDIO_OPT') 852 os.putenv('QEMU_AUDIO_DRV', self.get('QB_AUDIO_DRV')) 853 else: 854 os.putenv('QEMU_AUDIO_DRV', 'none') 855 856 self.check_qemu_system() 857 self.check_kvm() 858 self.check_fstype() 859 self.check_rootfs() 860 self.check_ovmf() 861 self.check_kernel() 862 self.check_dtb() 863 self.check_bios() 864 self.check_mem() 865 self.check_tcpserial() 866 867 def read_qemuboot(self): 868 if not self.qemuboot: 869 if self.get('DEPLOY_DIR_IMAGE'): 870 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 871 else: 872 logger.warning("Can't find qemuboot conf file, DEPLOY_DIR_IMAGE is NULL!") 873 return 874 875 if self.rootfs and not os.path.exists(self.rootfs): 876 # Lazy rootfs 877 machine = self.get('MACHINE') 878 if not machine: 879 machine = os.path.basename(deploy_dir_image) 880 self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image, 881 self.rootfs, machine) 882 else: 883 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image 884 logger.debug('Running %s...' % cmd) 885 try: 886 qbs = subprocess.check_output(cmd, shell=True).decode('utf-8') 887 except subprocess.CalledProcessError as err: 888 raise RunQemuError(err) 889 if qbs: 890 for qb in qbs.split(): 891 # Don't use initramfs when other choices unless fstype is ramfs 892 if '-initramfs-' in os.path.basename(qb) and self.fstype != 'cpio.gz': 893 continue 894 self.qemuboot = qb 895 break 896 if not self.qemuboot: 897 # Use the first one when no choice 898 self.qemuboot = qbs.split()[0] 899 self.qbconfload = True 900 901 if not self.qemuboot: 902 # If we haven't found a .qemuboot.conf at this point it probably 903 # doesn't exist, continue without 904 return 905 906 if not os.path.exists(self.qemuboot): 907 raise RunQemuError("Failed to find %s (wrong image name or BSP does not support running under qemu?)." % self.qemuboot) 908 909 logger.debug('CONFFILE: %s' % self.qemuboot) 910 911 cf = configparser.ConfigParser() 912 cf.read(self.qemuboot) 913 for k, v in cf.items('config_bsp'): 914 k_upper = k.upper() 915 if v.startswith("../"): 916 v = os.path.abspath(os.path.dirname(self.qemuboot) + "/" + v) 917 elif v == ".": 918 v = os.path.dirname(self.qemuboot) 919 self.set(k_upper, v) 920 921 def validate_paths(self): 922 """Ensure all relevant path variables are set""" 923 # When we're started with a *.qemuboot.conf arg assume that image 924 # artefacts are relative to that file, rather than in whatever 925 # directory DEPLOY_DIR_IMAGE in the conf file points to. 926 if self.qbconfload: 927 imgdir = os.path.realpath(os.path.dirname(self.qemuboot)) 928 if imgdir != os.path.realpath(self.get('DEPLOY_DIR_IMAGE')): 929 logger.info('Setting DEPLOY_DIR_IMAGE to folder containing %s (%s)' % (self.qemuboot, imgdir)) 930 self.set('DEPLOY_DIR_IMAGE', imgdir) 931 932 # If the STAGING_*_NATIVE directories from the config file don't exist 933 # and we're in a sourced OE build directory try to extract the paths 934 # from `bitbake -e` 935 havenative = os.path.exists(self.get('STAGING_DIR_NATIVE')) and \ 936 os.path.exists(self.get('STAGING_BINDIR_NATIVE')) 937 938 if not havenative: 939 if not self.bitbake_e: 940 self.load_bitbake_env() 941 942 if self.bitbake_e: 943 native_vars = ['STAGING_DIR_NATIVE'] 944 for nv in native_vars: 945 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M) 946 if s and s.group(1) != self.get(nv): 947 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1))) 948 self.set(nv, s.group(1)) 949 else: 950 # when we're invoked from a running bitbake instance we won't 951 # be able to call `bitbake -e`, then try: 952 # - get OE_TMPDIR from environment and guess paths based on it 953 # - get OECORE_NATIVE_SYSROOT from environment (for sdk) 954 tmpdir = self.get('OE_TMPDIR') 955 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT') 956 if tmpdir: 957 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir) 958 hostos, _, _, _, machine = os.uname() 959 buildsys = '%s-%s' % (machine, hostos.lower()) 960 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys) 961 self.set('STAGING_DIR_NATIVE', staging_dir_native) 962 elif oecore_native_sysroot: 963 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot) 964 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot) 965 if self.get('STAGING_DIR_NATIVE'): 966 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin 967 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE') 968 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native) 969 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')) 970 971 def print_config(self): 972 logoutput = ['Continuing with the following parameters:'] 973 if not self.fstype in self.vmtypes: 974 logoutput.append('KERNEL: [%s]' % self.kernel) 975 if self.bios: 976 logoutput.append('BIOS: [%s]' % self.bios) 977 if self.dtb: 978 logoutput.append('DTB: [%s]' % self.dtb) 979 logoutput.append('MACHINE: [%s]' % self.get('MACHINE')) 980 try: 981 fstype_flags = ' (' + ', '.join(self.fsinfo[self.fstype]) + ')' 982 except KeyError: 983 fstype_flags = '' 984 logoutput.append('FSTYPE: [%s%s]' % (self.fstype, fstype_flags)) 985 if self.fstype == 'nfs': 986 logoutput.append('NFS_DIR: [%s]' % self.rootfs) 987 else: 988 logoutput.append('ROOTFS: [%s]' % self.rootfs) 989 if self.ovmf_bios: 990 logoutput.append('OVMF: %s' % self.ovmf_bios) 991 if (self.ovmf_secboot_pkkek1): 992 logoutput.append('SECBOOT PKKEK1: [%s...]' % self.ovmf_secboot_pkkek1[0:100]) 993 logoutput.append('CONFFILE: [%s]' % self.qemuboot) 994 logoutput.append('') 995 logger.info('\n'.join(logoutput)) 996 997 def setup_nfs(self): 998 if not self.nfs_server: 999 if self.slirp_enabled: 1000 self.nfs_server = '10.0.2.2' 1001 else: 1002 self.nfs_server = '192.168.7.1' 1003 1004 # Figure out a new nfs_instance to allow multiple qemus running. 1005 ps = subprocess.check_output(("ps", "auxww")).decode('utf-8') 1006 pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) ' 1007 all_instances = re.findall(pattern, ps, re.M) 1008 if all_instances: 1009 all_instances.sort(key=int) 1010 self.nfs_instance = int(all_instances.pop()) + 1 1011 1012 nfsd_port = 3049 + 2 * self.nfs_instance 1013 mountd_port = 3048 + 2 * self.nfs_instance 1014 1015 # Export vars for runqemu-export-rootfs 1016 export_dict = { 1017 'NFS_INSTANCE': self.nfs_instance, 1018 'NFSD_PORT': nfsd_port, 1019 'MOUNTD_PORT': mountd_port, 1020 } 1021 for k, v in export_dict.items(): 1022 # Use '%s' since they are integers 1023 os.putenv(k, '%s' % v) 1024 1025 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port) 1026 1027 # Extract .tar.bz2 or .tar.bz if no nfs dir 1028 if not (self.rootfs and os.path.isdir(self.rootfs)): 1029 src_prefix = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME')) 1030 dest = "%s-nfsroot" % src_prefix 1031 if os.path.exists('%s.pseudo_state' % dest): 1032 logger.info('Use %s as NFS_DIR' % dest) 1033 self.rootfs = dest 1034 else: 1035 src = "" 1036 src1 = '%s.tar.bz2' % src_prefix 1037 src2 = '%s.tar.gz' % src_prefix 1038 if os.path.exists(src1): 1039 src = src1 1040 elif os.path.exists(src2): 1041 src = src2 1042 if not src: 1043 raise RunQemuError("No NFS_DIR is set, and can't find %s or %s to extract" % (src1, src2)) 1044 logger.info('NFS_DIR not found, extracting %s to %s' % (src, dest)) 1045 cmd = ('runqemu-extract-sdk', src, dest) 1046 logger.info('Running %s...' % str(cmd)) 1047 if subprocess.call(cmd) != 0: 1048 raise RunQemuError('Failed to run %s' % cmd) 1049 self.rootfs = dest 1050 self.cleanup_files.append(self.rootfs) 1051 self.cleanup_files.append('%s.pseudo_state' % self.rootfs) 1052 1053 # Start the userspace NFS server 1054 cmd = ('runqemu-export-rootfs', 'start', self.rootfs) 1055 logger.info('Running %s...' % str(cmd)) 1056 if subprocess.call(cmd) != 0: 1057 raise RunQemuError('Failed to run %s' % cmd) 1058 1059 self.nfs_running = True 1060 1061 def setup_net_bridge(self): 1062 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( 1063 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) 1064 1065 def setup_slirp(self): 1066 """Setup user networking""" 1067 1068 if self.fstype == 'nfs': 1069 self.setup_nfs() 1070 netconf = " " + self.cmdline_ip_slirp 1071 logger.info("Network configuration:%s", netconf) 1072 self.kernel_cmdline_script += netconf 1073 # Port mapping 1074 hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23" 1075 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) 1076 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default 1077 # Figure out the port 1078 ports = re.findall('hostfwd=[^-]*:([0-9]+)-[^,-]*', qb_slirp_opt) 1079 ports = [int(i) for i in ports] 1080 mac = 2 1081 1082 lockdir = "/tmp/qemu-port-locks" 1083 if not os.path.exists(lockdir): 1084 # There might be a race issue when multi runqemu processess are 1085 # running at the same time. 1086 try: 1087 os.mkdir(lockdir) 1088 os.chmod(lockdir, 0o777) 1089 except FileExistsError: 1090 pass 1091 1092 # Find a free port to avoid conflicts 1093 for p in ports[:]: 1094 p_new = p 1095 while not self.check_free_port('localhost', p_new, lockdir): 1096 p_new += 1 1097 mac += 1 1098 while p_new in ports: 1099 p_new += 1 1100 mac += 1 1101 if p != p_new: 1102 ports.append(p_new) 1103 qb_slirp_opt = re.sub(':%s-' % p, ':%s-' % p_new, qb_slirp_opt) 1104 logger.info("Port forward changed: %s -> %s" % (p, p_new)) 1105 mac = "%s%02x" % (self.mac_slirp, mac) 1106 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qb_slirp_opt)) 1107 # Print out port foward 1108 hostfwd = re.findall('(hostfwd=[^,]*)', qb_slirp_opt) 1109 if hostfwd: 1110 logger.info('Port forward: %s' % ' '.join(hostfwd)) 1111 1112 def setup_tap(self): 1113 """Setup tap""" 1114 1115 # This file is created when runqemu-gen-tapdevs creates a bank of tap 1116 # devices, indicating that the user should not bring up new ones using 1117 # sudo. 1118 nosudo_flag = '/etc/runqemu-nosudo' 1119 self.qemuifup = shutil.which('runqemu-ifup') 1120 self.qemuifdown = shutil.which('runqemu-ifdown') 1121 ip = shutil.which('ip') 1122 lockdir = "/tmp/qemu-tap-locks" 1123 1124 if not (self.qemuifup and self.qemuifdown and ip): 1125 logger.error("runqemu-ifup: %s" % self.qemuifup) 1126 logger.error("runqemu-ifdown: %s" % self.qemuifdown) 1127 logger.error("ip: %s" % ip) 1128 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") 1129 1130 if not os.path.exists(lockdir): 1131 # There might be a race issue when multi runqemu processess are 1132 # running at the same time. 1133 try: 1134 os.mkdir(lockdir) 1135 os.chmod(lockdir, 0o777) 1136 except FileExistsError: 1137 pass 1138 1139 cmd = (ip, 'link') 1140 logger.debug('Running %s...' % str(cmd)) 1141 ip_link = subprocess.check_output(cmd).decode('utf-8') 1142 # Matches line like: 6: tap0: <foo> 1143 possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M) 1144 tap = "" 1145 for p in possibles: 1146 lockfile = os.path.join(lockdir, p) 1147 if os.path.exists('%s.skip' % lockfile): 1148 logger.info('Found %s.skip, skipping %s' % (lockfile, p)) 1149 continue 1150 self.taplock = lockfile + '.lock' 1151 if self.acquire_taplock(error=False): 1152 tap = p 1153 logger.info("Using preconfigured tap device %s" % tap) 1154 logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap)) 1155 break 1156 1157 if not tap: 1158 if os.path.exists(nosudo_flag): 1159 logger.error("Error: There are no available tap devices to use for networking,") 1160 logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag) 1161 raise RunQemuError("a new one with sudo.") 1162 1163 gid = os.getgid() 1164 uid = os.getuid() 1165 logger.info("Setting up tap interface under sudo") 1166 cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native) 1167 try: 1168 tap = subprocess.check_output(cmd).decode('utf-8').strip() 1169 except subprocess.CalledProcessError as e: 1170 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e)) 1171 sys.exit(1) 1172 lockfile = os.path.join(lockdir, tap) 1173 self.taplock = lockfile + '.lock' 1174 self.acquire_taplock() 1175 self.cleantap = True 1176 logger.debug('Created tap: %s' % tap) 1177 1178 if not tap: 1179 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") 1180 sys.exit(1) 1181 self.tap = tap 1182 tapnum = int(tap[3:]) 1183 gateway = tapnum * 2 + 1 1184 client = gateway + 1 1185 if self.fstype == 'nfs': 1186 self.setup_nfs() 1187 netconf = " " + self.cmdline_ip_tap 1188 netconf = netconf.replace('@CLIENT@', str(client)) 1189 netconf = netconf.replace('@GATEWAY@', str(gateway)) 1190 logger.info("Network configuration:%s", netconf) 1191 self.kernel_cmdline_script += netconf 1192 mac = "%s%02x" % (self.mac_tap, client) 1193 qb_tap_opt = self.get('QB_TAP_OPT') 1194 if qb_tap_opt: 1195 qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap) 1196 else: 1197 qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap) 1198 1199 if self.vhost_enabled: 1200 qemu_tap_opt += ',vhost=on' 1201 1202 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) 1203 1204 def setup_network(self): 1205 if self.get('QB_NET') == 'none': 1206 return 1207 if sys.stdin.isatty(): 1208 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() 1209 self.network_device = self.get('QB_NETWORK_DEVICE') or self.network_device 1210 if self.net_bridge: 1211 self.setup_net_bridge() 1212 elif self.slirp_enabled: 1213 self.cmdline_ip_slirp = self.get('QB_CMDLINE_IP_SLIRP') or self.cmdline_ip_slirp 1214 self.setup_slirp() 1215 else: 1216 self.cmdline_ip_tap = self.get('QB_CMDLINE_IP_TAP') or self.cmdline_ip_tap 1217 self.setup_tap() 1218 1219 def setup_rootfs(self): 1220 if self.get('QB_ROOTFS') == 'none': 1221 return 1222 if 'wic.' in self.fstype: 1223 self.fstype = self.fstype[4:] 1224 rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' 1225 1226 tmpfsdir = os.environ.get("RUNQEMU_TMPFS_DIR", None) 1227 if self.snapshot and tmpfsdir: 1228 newrootfs = os.path.join(tmpfsdir, os.path.basename(self.rootfs)) + "." + str(os.getpid()) 1229 logger.info("Copying rootfs to %s" % newrootfs) 1230 copy_start = time.time() 1231 shutil.copyfile(self.rootfs, newrootfs) 1232 logger.info("Copy done in %s seconds" % (time.time() - copy_start)) 1233 self.rootfs = newrootfs 1234 # Don't need a second copy now! 1235 self.snapshot = False 1236 self.cleanup_files.append(newrootfs) 1237 1238 qb_rootfs_opt = self.get('QB_ROOTFS_OPT') 1239 if qb_rootfs_opt: 1240 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) 1241 else: 1242 self.rootfs_options = '-drive file=%s,if=virtio,format=%s' % (self.rootfs, rootfs_format) 1243 1244 qb_rootfs_extra_opt = self.get("QB_ROOTFS_EXTRA_OPT") 1245 if qb_rootfs_extra_opt and not qb_rootfs_extra_opt.startswith(","): 1246 qb_rootfs_extra_opt = "," + qb_rootfs_extra_opt 1247 1248 if self.fstype in ('cpio.gz', 'cpio'): 1249 self.kernel_cmdline = 'root=/dev/ram0 rw debugshell' 1250 self.rootfs_options = '-initrd %s' % self.rootfs 1251 else: 1252 vm_drive = '' 1253 if self.fstype in self.vmtypes: 1254 if self.fstype == 'iso': 1255 vm_drive = '-drive file=%s,if=virtio,media=cdrom' % self.rootfs 1256 elif self.get('QB_DRIVE_TYPE'): 1257 drive_type = self.get('QB_DRIVE_TYPE') 1258 if drive_type.startswith("/dev/sd"): 1259 logger.info('Using scsi drive') 1260 vm_drive = '-drive if=none,id=hd,file=%s,format=%s -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd%s' \ 1261 % (self.rootfs, rootfs_format, qb_rootfs_extra_opt) 1262 elif drive_type.startswith("/dev/hd"): 1263 logger.info('Using ide drive') 1264 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) 1265 elif drive_type.startswith("/dev/vdb"): 1266 logger.info('Using block virtio drive'); 1267 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ 1268 % (self.rootfs, rootfs_format,qb_rootfs_extra_opt) 1269 else: 1270 # virtio might have been selected explicitly (just use it), or 1271 # is used as fallback (then warn about that). 1272 if not drive_type.startswith("/dev/vd"): 1273 logger.warning("Unknown QB_DRIVE_TYPE: %s" % drive_type) 1274 logger.warning("Failed to figure out drive type, consider define or fix QB_DRIVE_TYPE") 1275 logger.warning('Trying to use virtio block drive') 1276 vm_drive = '-drive if=virtio,file=%s,format=%s' % (self.rootfs, rootfs_format) 1277 1278 # All branches above set vm_drive. 1279 self.rootfs_options = vm_drive 1280 if not self.fstype in self.vmtypes: 1281 self.rootfs_options += ' -no-reboot' 1282 1283 # By default, ' rw' is appended to QB_KERNEL_ROOT unless either ro or rw is explicitly passed. 1284 qb_kernel_root = self.get('QB_KERNEL_ROOT') 1285 qb_kernel_root_l = qb_kernel_root.split() 1286 if not ('ro' in qb_kernel_root_l or 'rw' in qb_kernel_root_l): 1287 qb_kernel_root += ' rw' 1288 self.kernel_cmdline = 'root=%s' % qb_kernel_root 1289 1290 if self.fstype == 'nfs': 1291 self.rootfs_options = '' 1292 k_root = '/dev/nfs nfsroot=%s:%s,%s' % (self.nfs_server, os.path.abspath(self.rootfs), self.unfs_opts) 1293 self.kernel_cmdline = 'root=%s rw' % k_root 1294 1295 if self.fstype == 'none': 1296 self.rootfs_options = '' 1297 1298 self.set('ROOTFS_OPTIONS', self.rootfs_options) 1299 1300 def guess_qb_system(self): 1301 """attempt to determine the appropriate qemu-system binary""" 1302 mach = self.get('MACHINE') 1303 if not mach: 1304 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' 1305 if self.rootfs: 1306 match = re.match(search, self.rootfs) 1307 if match: 1308 mach = match.group(1) 1309 elif self.kernel: 1310 match = re.match(search, self.kernel) 1311 if match: 1312 mach = match.group(1) 1313 1314 if not mach: 1315 return None 1316 1317 if mach == 'qemuarm': 1318 qbsys = 'arm' 1319 elif mach == 'qemuarm64': 1320 qbsys = 'aarch64' 1321 elif mach == 'qemux86': 1322 qbsys = 'i386' 1323 elif mach == 'qemux86-64': 1324 qbsys = 'x86_64' 1325 elif mach == 'qemuppc': 1326 qbsys = 'ppc' 1327 elif mach == 'qemumips': 1328 qbsys = 'mips' 1329 elif mach == 'qemumips64': 1330 qbsys = 'mips64' 1331 elif mach == 'qemumipsel': 1332 qbsys = 'mipsel' 1333 elif mach == 'qemumips64el': 1334 qbsys = 'mips64el' 1335 elif mach == 'qemuriscv64': 1336 qbsys = 'riscv64' 1337 elif mach == 'qemuriscv32': 1338 qbsys = 'riscv32' 1339 else: 1340 logger.error("Unable to determine QEMU PC System emulator for %s machine." % mach) 1341 logger.error("As %s is not among valid QEMU machines such as," % mach) 1342 logger.error("qemux86-64, qemux86, qemuarm64, qemuarm, qemumips64, qemumips64el, qemumipsel, qemumips, qemuppc") 1343 raise RunQemuError("Set qb_system_name with suitable QEMU PC System emulator in .*qemuboot.conf.") 1344 1345 return 'qemu-system-%s' % qbsys 1346 1347 def check_qemu_system(self): 1348 qemu_system = self.get('QB_SYSTEM_NAME') 1349 if not qemu_system: 1350 qemu_system = self.guess_qb_system() 1351 if not qemu_system: 1352 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") 1353 self.qemu_system = qemu_system 1354 1355 def setup_vga(self): 1356 if self.nographic == True: 1357 if self.sdl == True: 1358 raise RunQemuError('Option nographic makes no sense alongside the sdl option.') 1359 if self.gtk == True: 1360 raise RunQemuError('Option nographic makes no sense alongside the gtk option.') 1361 self.qemu_opt += ' -nographic' 1362 1363 if self.novga == True: 1364 self.qemu_opt += ' -vga none' 1365 return 1366 1367 if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False): 1368 raise RunQemuError('Option gl/gl-es needs gtk or sdl option.') 1369 1370 # If we have no display option, we autodetect based upon what qemu supports. We 1371 # need our font setup and show-cusor below so we need to see what qemu --help says 1372 # is supported so we can pass our correct config in. 1373 if not self.nographic and not self.sdl and not self.gtk and not self.publicvnc and not self.egl_headless == True: 1374 output = subprocess.check_output([self.qemu_bin, "--help"], universal_newlines=True, env=self.qemu_environ) 1375 if "-display gtk" in output: 1376 self.gtk = True 1377 elif "-display sdl" in output: 1378 self.sdl = True 1379 else: 1380 self.qemu_opt += ' -display none' 1381 1382 if self.sdl == True or self.gtk == True or self.egl_headless == True: 1383 1384 if self.qemu_system.endswith(('i386', 'x86_64')): 1385 if self.gl or self.gl_es or self.egl_headless: 1386 self.qemu_opt += ' -device virtio-vga-gl ' 1387 else: 1388 self.qemu_opt += ' -device virtio-vga ' 1389 1390 self.qemu_opt += ' -display ' 1391 if self.egl_headless == True: 1392 self.set_dri_path() 1393 self.qemu_opt += 'egl-headless,' 1394 else: 1395 if self.sdl == True: 1396 self.qemu_opt += 'sdl,' 1397 elif self.gtk == True: 1398 self.qemu_environ['FONTCONFIG_PATH'] = '/etc/fonts' 1399 self.qemu_opt += 'gtk,' 1400 1401 if self.gl == True: 1402 self.set_dri_path() 1403 self.qemu_opt += 'gl=on,' 1404 elif self.gl_es == True: 1405 self.set_dri_path() 1406 self.qemu_opt += 'gl=es,' 1407 self.qemu_opt += 'show-cursor=on' 1408 1409 self.qemu_opt += ' %s' %self.get('QB_GRAPHICS') 1410 1411 def setup_serial(self): 1412 # Setup correct kernel command line for serial 1413 if self.get('SERIAL_CONSOLES') and (self.serialstdio == True or self.serialconsole == True or self.nographic == True or self.tcpserial_portnum): 1414 for entry in self.get('SERIAL_CONSOLES').split(' '): 1415 self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1] 1416 1417 if self.serialstdio == True or self.nographic == True: 1418 self.qemu_opt += " -serial mon:stdio" 1419 else: 1420 self.qemu_opt += " -serial mon:vc" 1421 if self.serialconsole: 1422 if sys.stdin.isatty(): 1423 subprocess.check_call(("stty", "intr", "^]")) 1424 logger.info("Interrupt character is '^]'") 1425 1426 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") 1427 1428 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES). 1429 # If no serial or serialtcp options were specified, only ttyS0 is created 1430 # and sysvinit shows an error trying to enable ttyS1: 1431 # INIT: Id "S1" respawning too fast: disabled for 5 minutes 1432 serial_num = len(re.findall("-serial", self.qemu_opt)) 1433 if serial_num < 2: 1434 self.qemu_opt += " -serial null" 1435 1436 def find_qemu(self): 1437 qemu_bin = os.path.join(self.bindir_native, self.qemu_system) 1438 1439 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't 1440 # find QEMU in sysroot, it needs to use host's qemu. 1441 if not os.path.exists(qemu_bin): 1442 logger.info("QEMU binary not found in %s, trying host's QEMU" % qemu_bin) 1443 for path in (os.environ['PATH'] or '').split(':'): 1444 qemu_bin_tmp = os.path.join(path, self.qemu_system) 1445 logger.info("Trying: %s" % qemu_bin_tmp) 1446 if os.path.exists(qemu_bin_tmp): 1447 qemu_bin = qemu_bin_tmp 1448 if not os.path.isabs(qemu_bin): 1449 qemu_bin = os.path.abspath(qemu_bin) 1450 logger.info("Using host's QEMU: %s" % qemu_bin) 1451 break 1452 1453 if not os.access(qemu_bin, os.X_OK): 1454 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) 1455 self.qemu_bin = qemu_bin 1456 1457 def setup_final(self): 1458 1459 self.find_qemu() 1460 1461 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'))) 1462 1463 for ovmf in self.ovmf_bios: 1464 format = ovmf.rsplit('.', 1)[-1] 1465 if format == "bin": 1466 format = "raw" 1467 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) 1468 1469 self.qemu_opt += ' ' + self.qemu_opt_script 1470 1471 if self.ovmf_secboot_pkkek1: 1472 # Provide the Platform Key and first Key Exchange Key certificate as an 1473 # OEM string in the SMBIOS Type 11 table. Prepend the certificate string 1474 # with "application prefix" of the EnrollDefaultKeys.efi application 1475 self.qemu_opt += ' -smbios type=11,value=4e32566d-8e9e-4f52-81d3-5bb9715f9727:' \ 1476 + self.ovmf_secboot_pkkek1 1477 1478 # Append qemuparams to override previous settings 1479 if self.qemuparams: 1480 self.qemu_opt += ' ' + self.qemuparams 1481 1482 if self.snapshot: 1483 self.qemu_opt += " -snapshot" 1484 1485 self.setup_serial() 1486 self.setup_vga() 1487 1488 def start_qemu(self): 1489 import shlex 1490 if self.kernel: 1491 kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline, 1492 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), 1493 self.bootparams) 1494 if self.dtb: 1495 kernel_opts += " -dtb %s" % self.dtb 1496 else: 1497 kernel_opts = "" 1498 1499 if self.bios: 1500 self.qemu_opt += " -bios %s" % self.bios 1501 1502 cmd = "%s %s" % (self.qemu_opt, kernel_opts) 1503 cmds = shlex.split(cmd) 1504 logger.info('Running %s\n' % cmd) 1505 with open('/proc/uptime', 'r') as f: 1506 uptime_seconds = f.readline().split()[0] 1507 logger.info('Host uptime: %s\n' % uptime_seconds) 1508 pass_fds = [] 1509 if self.taplock_descriptor: 1510 pass_fds = [self.taplock_descriptor.fileno()] 1511 if len(self.portlocks): 1512 for descriptor in self.portlocks.values(): 1513 pass_fds.append(descriptor.fileno()) 1514 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ) 1515 self.qemuprocess = process 1516 retcode = process.wait() 1517 if retcode: 1518 if retcode == -signal.SIGTERM: 1519 logger.info("Qemu terminated by SIGTERM") 1520 else: 1521 logger.error("Failed to run qemu: %s", process.stderr.read().decode()) 1522 1523 def cleanup(self): 1524 if self.cleaned: 1525 return 1526 1527 # avoid dealing with SIGTERM when cleanup function is running 1528 signal.signal(signal.SIGTERM, signal.SIG_IGN) 1529 1530 logger.info("Cleaning up") 1531 1532 if self.qemuprocess: 1533 try: 1534 # give it some time to shut down, ignore return values and output 1535 self.qemuprocess.send_signal(signal.SIGTERM) 1536 self.qemuprocess.communicate(timeout=5) 1537 except subprocess.TimeoutExpired: 1538 self.qemuprocess.kill() 1539 1540 with open('/proc/uptime', 'r') as f: 1541 uptime_seconds = f.readline().split()[0] 1542 logger.info('Host uptime: %s\n' % uptime_seconds) 1543 if self.cleantap: 1544 cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native) 1545 logger.debug('Running %s' % str(cmd)) 1546 subprocess.check_call(cmd) 1547 self.release_taplock() 1548 self.release_portlock() 1549 1550 if self.nfs_running: 1551 logger.info("Shutting down the userspace NFS server...") 1552 cmd = ("runqemu-export-rootfs", "stop", self.rootfs) 1553 logger.debug('Running %s' % str(cmd)) 1554 subprocess.check_call(cmd) 1555 1556 if self.saved_stty: 1557 subprocess.check_call(("stty", self.saved_stty)) 1558 1559 if self.cleanup_files: 1560 for ent in self.cleanup_files: 1561 logger.info('Removing %s' % ent) 1562 if os.path.isfile(ent): 1563 os.remove(ent) 1564 else: 1565 shutil.rmtree(ent) 1566 1567 # Deliberately ignore the return code of 'tput smam'. 1568 subprocess.call(["tput", "smam"]) 1569 1570 self.cleaned = True 1571 1572 def run_bitbake_env(self, mach=None): 1573 bitbake = shutil.which('bitbake') 1574 if not bitbake: 1575 return 1576 1577 if not mach: 1578 mach = self.get('MACHINE') 1579 1580 multiconfig = self.get('MULTICONFIG') 1581 if multiconfig: 1582 multiconfig = "mc:%s" % multiconfig 1583 1584 if mach: 1585 cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig) 1586 else: 1587 cmd = 'bitbake -e %s' % multiconfig 1588 1589 logger.info('Running %s...' % cmd) 1590 return subprocess.check_output(cmd, shell=True).decode('utf-8') 1591 1592 def load_bitbake_env(self, mach=None): 1593 if self.bitbake_e: 1594 return 1595 1596 try: 1597 self.bitbake_e = self.run_bitbake_env(mach=mach) 1598 except subprocess.CalledProcessError as err: 1599 self.bitbake_e = '' 1600 logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8')) 1601 1602 def validate_combos(self): 1603 if (self.fstype in self.vmtypes) and self.kernel: 1604 raise RunQemuError("%s doesn't need kernel %s!" % (self.fstype, self.kernel)) 1605 1606 @property 1607 def bindir_native(self): 1608 result = self.get('STAGING_BINDIR_NATIVE') 1609 if result and os.path.exists(result): 1610 return result 1611 1612 cmd = ['bitbake', '-e'] 1613 multiconfig = self.get('MULTICONFIG') 1614 if multiconfig: 1615 cmd.append('mc:%s:qemu-helper-native' % multiconfig) 1616 else: 1617 cmd.append('qemu-helper-native') 1618 1619 logger.info('Running %s...' % str(cmd)) 1620 out = subprocess.check_output(cmd).decode('utf-8') 1621 1622 match = re.search('^STAGING_BINDIR_NATIVE="(.*)"', out, re.M) 1623 if match: 1624 result = match.group(1) 1625 if os.path.exists(result): 1626 self.set('STAGING_BINDIR_NATIVE', result) 1627 return result 1628 raise RunQemuError("Native sysroot directory %s doesn't exist" % result) 1629 else: 1630 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd) 1631 1632 1633def main(): 1634 if "help" in sys.argv or '-h' in sys.argv or '--help' in sys.argv: 1635 print_usage() 1636 return 0 1637 try: 1638 config = BaseConfig() 1639 1640 renice = os.path.expanduser("~/bin/runqemu-renice") 1641 if os.path.exists(renice): 1642 logger.info('Using %s to renice' % renice) 1643 subprocess.check_call([renice, str(os.getpid())]) 1644 1645 def sigterm_handler(signum, frame): 1646 logger.info("Received signal: %s" % (signum)) 1647 config.cleanup() 1648 signal.signal(signal.SIGTERM, sigterm_handler) 1649 1650 config.check_args() 1651 config.read_qemuboot() 1652 config.check_and_set() 1653 # Check whether the combos is valid or not 1654 config.validate_combos() 1655 config.print_config() 1656 config.setup_network() 1657 config.setup_rootfs() 1658 config.setup_final() 1659 config.start_qemu() 1660 except RunQemuError as err: 1661 logger.error(err) 1662 return 1 1663 except Exception as err: 1664 import traceback 1665 traceback.print_exc() 1666 return 1 1667 finally: 1668 config.cleanup() 1669 1670if __name__ == "__main__": 1671 sys.exit(main()) 1672