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 nonetwork - disable network connectivity 70 novga - Disable VGA emulation completely 71 sdl - choose the SDL UI frontend 72 gtk - choose the Gtk UI frontend 73 gl - enable virgl-based GL acceleration (also needs gtk or sdl options) 74 gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) 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) 78 serial - enable a serial console on /dev/ttyS0 79 serialstdio - enable a serial console on the console (regardless of graphics mode) 80 slirp - enable user networking, no root privilege is required 81 snapshot - don't write changes back to images 82 kvm - enable KVM 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) 84 publicvnc - enable a VNC server open to all hosts 85 audio - enable audio 86 guestagent - enable guest agent communication 87 qmp=<path> - create a QMP socket (defaults to unix:qmp.sock if unspecified) 88 [*/]ovmf* - OVMF firmware file or base name for booting with UEFI 89 tcpserial=<port> - specify tcp serial port number 90 qemuparams=<xyz> - specify custom parameters to QEMU 91 bootparams=<xyz> - specify custom kernel parameters during boot 92 help, -h, --help: print this text 93 -d, --debug: Enable debug output 94 -q, --quiet: Hide most output except error messages 95 96Examples: 97 runqemu 98 runqemu qemuarm 99 runqemu tmp/deploy/images/qemuarm 100 runqemu tmp/deploy/images/qemux86/<qemuboot.conf> 101 runqemu qemux86-64 core-image-sato ext4 102 runqemu qemux86-64 wic-image-minimal wic 103 runqemu path/to/bzImage-qemux86.bin path/to/nfsrootdir/ serial 104 runqemu qemux86 iso/hddimg/wic.vmdk/wic.vhd/wic.vhdx/wic.qcow2/wic.vdi/ramfs/cpio.gz... 105 runqemu qemux86 qemuparams="-m 256" 106 runqemu qemux86 bootparams="psplash=false" 107 runqemu path/to/<image>-<machine>.wic 108 runqemu path/to/<image>-<machine>.wic.vmdk 109 runqemu path/to/<image>-<machine>.wic.vhdx 110 runqemu path/to/<image>-<machine>.wic.vhd 111""") 112 113def check_tun(): 114 """Check /dev/net/tun""" 115 dev_tun = '/dev/net/tun' 116 if not os.path.exists(dev_tun): 117 raise RunQemuError("TUN control device %s is unavailable; you may need to enable TUN (e.g. sudo modprobe tun)" % dev_tun) 118 119 if not os.access(dev_tun, os.W_OK): 120 raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) 121 122def get_first_file(globs): 123 """Return first file found in wildcard globs""" 124 for g in globs: 125 all_files = glob.glob(g) 126 if all_files: 127 for f in all_files: 128 if not os.path.isdir(f): 129 return f 130 return '' 131 132class BaseConfig(object): 133 def __init__(self): 134 # The self.d saved vars from self.set(), part of them are from qemuboot.conf 135 self.d = {'QB_KERNEL_ROOT': '/dev/vda'} 136 137 # Supported env vars, add it here if a var can be got from env, 138 # and don't use os.getenv in the code. 139 self.env_vars = ('MACHINE', 140 'ROOTFS', 141 'KERNEL', 142 'BIOS', 143 'DEVICE_TREE', 144 'DEPLOY_DIR_IMAGE', 145 'OE_TMPDIR', 146 'OECORE_NATIVE_SYSROOT', 147 'MULTICONFIG', 148 'SERIAL_CONSOLES', 149 ) 150 151 self.qemu_opt = '' 152 self.qemu_opt_script = '' 153 self.qemuparams = '' 154 self.nfs_server = '' 155 self.rootfs = '' 156 # File name(s) of a OVMF firmware file or variable store, 157 # to be added with -drive if=pflash. 158 # Found in the same places as the rootfs, with or without one of 159 # these suffices: qcow2, bin. 160 self.ovmf_bios = [] 161 # When enrolling default Secure Boot keys, the hypervisor 162 # must provide the Platform Key and the first Key Exchange Key 163 # certificate in the Type 11 SMBIOS table. 164 self.ovmf_secboot_pkkek1 = '' 165 self.qemuboot = '' 166 self.qbconfload = False 167 self.kernel = '' 168 self.bios = '' 169 self.kernel_cmdline = '' 170 self.kernel_cmdline_script = '' 171 self.bootparams = '' 172 self.dtb = '' 173 self.fstype = '' 174 self.kvm_enabled = False 175 self.vhost_enabled = False 176 self.slirp_enabled = False 177 self.net_bridge = None 178 self.nfs_instance = 0 179 self.nfs_running = False 180 self.serialconsole = 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 191 self.cleantap = False 192 self.saved_stty = '' 193 self.audio_enabled = False 194 self.tcpserial_portnum = '' 195 self.taplock = '' 196 self.taplock_descriptor = None 197 self.portlocks = {} 198 self.bitbake_e = '' 199 self.snapshot = False 200 self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") 201 self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', 202 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz', 203 'squashfs', 'squashfs-xz', 'squashfs-lzo', 204 'squashfs-lz4', 'squashfs-zst') 205 self.vmtypes = ('hddimg', 'iso') 206 self.fsinfo = {} 207 self.network_device = "-device e1000,netdev=net0,mac=@MAC@" 208 self.cmdline_ip_slirp = "ip=dhcp" 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" 210 # Use different mac section for tap and slirp to avoid 211 # conflicts, e.g., when one is running with tap, the other is 212 # running with slirp. 213 # The last section is dynamic, which is for avoiding conflicts, 214 # when multiple qemus are running, e.g., when multiple tap or 215 # slirp qemus are running. 216 self.mac_tap = "52:54:00:12:34:" 217 self.mac_slirp = "52:54:00:12:35:" 218 # pid of the actual qemu process 219 self.qemu_environ = os.environ.copy() 220 self.qemuprocess = None 221 # avoid cleanup twice 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' 228 229 def acquire_taplock(self, error=True): 230 logger.debug("Acquiring lockfile %s..." % self.taplock) 231 try: 232 self.taplock_descriptor = open(self.taplock, 'w') 233 fcntl.flock(self.taplock_descriptor, fcntl.LOCK_EX|fcntl.LOCK_NB) 234 except Exception as e: 235 msg = "Acquiring lockfile %s failed: %s" % (self.taplock, e) 236 if error: 237 logger.error(msg) 238 else: 239 logger.info(msg) 240 if self.taplock_descriptor: 241 self.taplock_descriptor.close() 242 self.taplock_descriptor = None 243 return False 244 return True 245 246 def release_taplock(self): 247 if self.taplock_descriptor: 248 logger.debug("Releasing lockfile for tap device '%s'" % self.tap) 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) 252 self.taplock_descriptor.close() 253 # Removing the file is a potential race, don't do that either 254 # os.remove(self.taplock) 255 self.taplock_descriptor = None 256 257 def check_free_port(self, host, port, lockdir): 258 """ Check whether the port is free or not """ 259 import socket 260 from contextlib import closing 261 262 lockfile = os.path.join(lockdir, str(port) + '.lock') 263 if self.acquire_portlock(lockfile): 264 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: 265 if sock.connect_ex((host, port)) == 0: 266 # Port is open, so not free 267 self.release_portlock(lockfile) 268 return False 269 else: 270 # Port is not open, so free 271 return True 272 else: 273 return False 274 275 def acquire_portlock(self, lockfile): 276 logger.debug("Acquiring lockfile %s..." % lockfile) 277 try: 278 portlock_descriptor = open(lockfile, 'w') 279 self.portlocks.update({lockfile: portlock_descriptor}) 280 fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_EX|fcntl.LOCK_NB) 281 except Exception as e: 282 msg = "Acquiring lockfile %s failed: %s" % (lockfile, e) 283 logger.info(msg) 284 if lockfile in self.portlocks.keys() and self.portlocks[lockfile]: 285 self.portlocks[lockfile].close() 286 del self.portlocks[lockfile] 287 return False 288 return True 289 290 def release_portlock(self, lockfile=None): 291 if lockfile != None: 292 logger.debug("Releasing lockfile '%s'" % lockfile) 293 # We pass the fd to the qemu process and if we unlock here, it would unlock for 294 # that too. Therefore don't unlock, just close 295 # fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) 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] 300 elif len(self.portlocks): 301 for lockfile, descriptor in self.portlocks.items(): 302 logger.debug("Releasing lockfile '%s'" % lockfile) 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) 306 descriptor.close() 307 # Removing the file is a potential race, don't do that either 308 # os.remove(lockfile) 309 self.portlocks = {} 310 311 def get(self, key): 312 if key in self.d: 313 return self.d.get(key) 314 elif os.getenv(key): 315 return os.getenv(key) 316 else: 317 return '' 318 319 def set(self, key, value): 320 self.d[key] = value 321 322 def is_deploy_dir_image(self, p): 323 if os.path.isdir(p): 324 if not re.search('.qemuboot.conf$', '\n'.join(os.listdir(p)), re.M): 325 logger.debug("Can't find required *.qemuboot.conf in %s" % p) 326 return False 327 if not any(map(lambda name: '-image-' in name, os.listdir(p))): 328 logger.debug("Can't find *-image-* in %s" % p) 329 return False 330 return True 331 else: 332 return False 333 334 def check_arg_fstype(self, fst): 335 """Check and set FSTYPE""" 336 if fst not in self.fstypes + self.vmtypes + self.wictypes: 337 logger.warning("Maybe unsupported FSTYPE: %s" % fst) 338 if not self.fstype or self.fstype == fst: 339 if fst == 'ramfs': 340 fst = 'cpio.gz' 341 if fst in ('tar.bz2', 'tar.gz'): 342 fst = 'nfs' 343 self.fstype = fst 344 else: 345 raise RunQemuError("Conflicting: FSTYPE %s and %s" % (self.fstype, fst)) 346 347 def set_machine_deploy_dir(self, machine, deploy_dir_image): 348 """Set MACHINE and DEPLOY_DIR_IMAGE""" 349 logger.debug('MACHINE: %s' % machine) 350 self.set("MACHINE", machine) 351 logger.debug('DEPLOY_DIR_IMAGE: %s' % deploy_dir_image) 352 self.set("DEPLOY_DIR_IMAGE", deploy_dir_image) 353 354 def check_arg_nfs(self, p): 355 if os.path.isdir(p): 356 self.rootfs = p 357 else: 358 m = re.match('(.*):(.*)', p) 359 self.nfs_server = m.group(1) 360 self.rootfs = m.group(2) 361 self.check_arg_fstype('nfs') 362 363 def check_arg_path(self, p): 364 """ 365 - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf 366 - Check whether it is a kernel file 367 - Check whether it is an image file 368 - Check whether it is an NFS dir 369 - Check whether it is an OVMF flash file 370 """ 371 if p.endswith('.qemuboot.conf'): 372 self.qemuboot = p 373 self.qbconfload = True 374 elif re.search('\\.bin$', p) or re.search('bzImage', p) or \ 375 re.search('zImage', p) or re.search('vmlinux', p) or \ 376 re.search('fitImage', p) or re.search('uImage', p): 377 self.kernel = p 378 elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)): 379 self.rootfs = p 380 # Check filename against self.fstypes can handle <file>.cpio.gz, 381 # otherwise, its type would be "gz", which is incorrect. 382 fst = "" 383 for t in self.fstypes: 384 if p.endswith(t): 385 fst = t 386 break 387 if not fst: 388 m = re.search('.*\\.(.*)$', self.rootfs) 389 if m: 390 fst = m.group(1) 391 if fst: 392 self.check_arg_fstype(fst) 393 qb = re.sub('\\.' + fst + "$", '.qemuboot.conf', self.rootfs) 394 if os.path.exists(qb): 395 self.qemuboot = qb 396 self.qbconfload = True 397 else: 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) 406 else: 407 raise RunQemuError("Can't find FSTYPE from: %s" % p) 408 409 elif os.path.isdir(p) or re.search(':', p) and re.search('/', p): 410 if self.is_deploy_dir_image(p): 411 logger.debug('DEPLOY_DIR_IMAGE: %s' % p) 412 self.set("DEPLOY_DIR_IMAGE", p) 413 else: 414 logger.debug("Assuming %s is an nfs rootfs" % p) 415 self.check_arg_nfs(p) 416 elif os.path.basename(p).startswith('ovmf'): 417 self.ovmf_bios.append(p) 418 else: 419 raise RunQemuError("Unknown path arg %s" % p) 420 421 def check_arg_machine(self, arg): 422 """Check whether it is a machine""" 423 if self.get('MACHINE') == arg: 424 return 425 elif self.get('MACHINE') and self.get('MACHINE') != arg: 426 raise RunQemuError("Maybe conflicted MACHINE: %s vs %s" % (self.get('MACHINE'), arg)) 427 elif re.search('/', arg): 428 raise RunQemuError("Unknown arg: %s" % arg) 429 430 logger.debug('Assuming MACHINE = %s' % arg) 431 432 # if we're running under testimage, or similarly as a child 433 # of an existing bitbake invocation, we can't invoke bitbake 434 # to validate the MACHINE setting and must assume it's correct... 435 # FIXME: testimage.bbclass exports these two variables into env, 436 # are there other scenarios in which we need to support being 437 # invoked by bitbake? 438 deploy = self.get('DEPLOY_DIR_IMAGE') 439 image_link_name = self.get('IMAGE_LINK_NAME') 440 bbchild = deploy and self.get('OE_TMPDIR') 441 if bbchild: 442 self.set_machine_deploy_dir(arg, deploy) 443 return 444 # also check whether we're running under a sourced toolchain 445 # environment file 446 if self.get('OECORE_NATIVE_SYSROOT'): 447 self.set("MACHINE", arg) 448 return 449 450 self.bitbake_e = self.run_bitbake_env(arg) 451 # bitbake -e doesn't report invalid MACHINE as an error, so 452 # let's check DEPLOY_DIR_IMAGE to make sure that it is a valid 453 # MACHINE. 454 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 455 if s: 456 deploy_dir_image = s.group(1) 457 else: 458 raise RunQemuError("bitbake -e %s" % self.bitbake_e) 459 if self.is_deploy_dir_image(deploy_dir_image): 460 self.set_machine_deploy_dir(arg, deploy_dir_image) 461 else: 462 logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) 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) 470 471 def set_dri_path(self): 472 drivers_path = os.path.join(self.bindir_native, '../lib/dri') 473 if not os.path.exists(drivers_path) or not os.listdir(drivers_path): 474 raise RunQemuError(""" 475qemu has been built without opengl support and accelerated graphics support is not available. 476To enable it, add: 477DISTRO_FEATURES_NATIVE:append = " opengl" 478DISTRO_FEATURES_NATIVESDK:append = " opengl" 479to your build configuration. 480""") 481 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path 482 483 def check_args(self): 484 for debug in ("-d", "--debug"): 485 if debug in sys.argv: 486 logger.setLevel(logging.DEBUG) 487 sys.argv.remove(debug) 488 489 for quiet in ("-q", "--quiet"): 490 if quiet in sys.argv: 491 logger.setLevel(logging.ERROR) 492 sys.argv.remove(quiet) 493 494 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: 495 self.qemu_environ['SDL_RENDER_DRIVER'] = 'software' 496 self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false' 497 498 unknown_arg = "" 499 for arg in sys.argv[1:]: 500 if arg in self.fstypes + self.vmtypes + self.wictypes: 501 self.check_arg_fstype(arg) 502 elif arg == 'nographic': 503 self.nographic = True 504 elif arg == "nonetwork": 505 self.nonetwork = True 506 elif arg == 'sdl': 507 self.sdl = True 508 elif arg == 'gtk': 509 self.gtk = True 510 elif arg == 'gl': 511 self.gl = True 512 elif arg == 'gl-es': 513 self.gl_es = True 514 elif arg == 'egl-headless': 515 self.egl_headless = True 516 elif arg == 'novga': 517 self.novga = True 518 elif arg == 'serial': 519 self.serialconsole = True 520 elif arg == "serialstdio": 521 self.serialstdio = True 522 elif arg == 'audio': 523 logger.info("Enabling audio in qemu") 524 logger.info("Please install sound drivers in linux host") 525 self.audio_enabled = True 526 elif arg == 'kvm': 527 self.kvm_enabled = True 528 elif arg == 'kvm-vhost': 529 self.vhost_enabled = True 530 elif arg == 'slirp': 531 self.slirp_enabled = True 532 elif arg.startswith('bridge='): 533 self.net_bridge = '%s' % arg[len('bridge='):] 534 elif arg == 'snapshot': 535 self.snapshot = True 536 elif arg == 'publicvnc': 537 self.publicvnc = True 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='):] 547 elif arg.startswith('tcpserial='): 548 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] 549 elif arg.startswith('qemuparams='): 550 self.qemuparams = ' %s' % arg[len('qemuparams='):] 551 elif arg.startswith('bootparams='): 552 self.bootparams = arg[len('bootparams='):] 553 elif os.path.exists(arg) or (re.search(':', arg) and re.search('/', arg)): 554 self.check_arg_path(os.path.abspath(arg)) 555 elif re.search(r'-image-|-image$', arg): 556 # Lazy rootfs 557 self.rootfs = arg 558 elif arg.startswith('ovmf'): 559 self.ovmf_bios.append(arg) 560 else: 561 # At last, assume it is the MACHINE 562 if (not unknown_arg) or unknown_arg == arg: 563 unknown_arg = arg 564 else: 565 raise RunQemuError("Can't handle two unknown args: %s %s\n" 566 "Try 'runqemu help' on how to use it" % \ 567 (unknown_arg, arg)) 568 # Check to make sure it is a valid machine 569 if unknown_arg and self.get('MACHINE') != unknown_arg: 570 if self.get('DEPLOY_DIR_IMAGE'): 571 machine = os.path.basename(self.get('DEPLOY_DIR_IMAGE')) 572 if unknown_arg == machine: 573 self.set("MACHINE", machine) 574 575 self.check_arg_machine(unknown_arg) 576 577 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): 578 self.load_bitbake_env(target=self.rootfs) 579 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) 580 if s: 581 self.set("DEPLOY_DIR_IMAGE", s.group(1)) 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 590 def check_kvm(self): 591 """Check kvm and kvm-host""" 592 if not (self.kvm_enabled or self.vhost_enabled): 593 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP')) 594 return 595 596 if not self.get('QB_CPU_KVM'): 597 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") 598 599 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP')) 600 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" 601 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" 602 dev_kvm = '/dev/kvm' 603 dev_vhost = '/dev/vhost-net' 604 if self.qemu_system.endswith(('i386', 'x86_64')): 605 with open('/proc/cpuinfo', 'r') as f: 606 kvm_cap = re.search('vmx|svm', "".join(f.readlines())) 607 if not kvm_cap: 608 logger.error("You are trying to enable KVM on a cpu without VT support.") 609 logger.error("Remove kvm from the command-line, or refer:") 610 raise RunQemuError(yocto_kvm_wiki) 611 612 if not os.path.exists(dev_kvm): 613 logger.error("Missing KVM device. Have you inserted kvm modules?") 614 logger.error("For further help see:") 615 raise RunQemuError(yocto_kvm_wiki) 616 617 if os.access(dev_kvm, os.W_OK|os.R_OK): 618 self.qemu_opt_script += ' -enable-kvm' 619 else: 620 logger.error("You have no read or write permission on /dev/kvm.") 621 logger.error("Please change the ownership of this file as described at:") 622 raise RunQemuError(yocto_kvm_wiki) 623 624 if self.vhost_enabled: 625 if not os.path.exists(dev_vhost): 626 logger.error("Missing virtio net device. Have you inserted vhost-net module?") 627 logger.error("For further help see:") 628 raise RunQemuError(yocto_paravirt_kvm_wiki) 629 630 if not os.access(dev_vhost, os.W_OK|os.R_OK): 631 logger.error("You have no read or write permission on /dev/vhost-net.") 632 logger.error("Please change the ownership of this file as described at:") 633 raise RunQemuError(yocto_paravirt_kvm_wiki) 634 635 def check_fstype(self): 636 """Check and setup FSTYPE""" 637 if not self.fstype: 638 fstype = self.get('QB_DEFAULT_FSTYPE') 639 if fstype: 640 self.fstype = fstype 641 else: 642 raise RunQemuError("FSTYPE is NULL!") 643 644 # parse QB_FSINFO into dict, e.g. { 'wic': ['no-kernel-in-fs', 'a-flag'], 'ext4': ['another-flag']} 645 wic_fs = False 646 qb_fsinfo = self.get('QB_FSINFO') 647 if qb_fsinfo: 648 qb_fsinfo = qb_fsinfo.split() 649 for fsinfo in qb_fsinfo: 650 try: 651 fstype, fsflag = fsinfo.split(':') 652 653 if fstype == 'wic': 654 if fsflag == 'no-kernel-in-fs': 655 wic_fs = True 656 elif fsflag == 'kernel-in-fs': 657 wic_fs = False 658 else: 659 logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) 660 continue 661 else: 662 logger.warning('QB_FSINFO is not supported for image type "%s"', fstype) 663 continue 664 665 if fstype in self.fsinfo: 666 self.fsinfo[fstype].append(fsflag) 667 else: 668 self.fsinfo[fstype] = [fsflag] 669 except Exception: 670 logger.error('Invalid parameter "%s" in QB_FSINFO', fsinfo) 671 672 # treat wic images as vmimages (with kernel) or as fsimages (rootfs only) 673 if wic_fs: 674 self.fstypes = self.fstypes + self.wictypes 675 else: 676 self.vmtypes = self.vmtypes + self.wictypes 677 678 def check_rootfs(self): 679 """Check and set rootfs""" 680 681 if self.fstype == "none": 682 return 683 684 if self.get('ROOTFS'): 685 if not self.rootfs: 686 self.rootfs = self.get('ROOTFS') 687 elif self.get('ROOTFS') != self.rootfs: 688 raise RunQemuError("Maybe conflicted ROOTFS: %s vs %s" % (self.get('ROOTFS'), self.rootfs)) 689 690 if self.fstype == 'nfs': 691 return 692 693 if self.rootfs and not os.path.exists(self.rootfs): 694 # Lazy rootfs 695 self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), 696 self.get('IMAGE_LINK_NAME'), 697 self.fstype) 698 elif not self.rootfs: 699 glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) 700 glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) 701 globs = (glob_name, glob_link) 702 self.rootfs = get_first_file(globs) 703 if not self.rootfs: 704 raise RunQemuError("Failed to find rootfs: %s or %s" % globs) 705 706 if not os.path.exists(self.rootfs): 707 raise RunQemuError("Can't find rootfs: %s" % self.rootfs) 708 709 def setup_pkkek1(self): 710 """ 711 Extract from PEM certificate the Platform Key and first Key 712 Exchange Key certificate string. The hypervisor needs to provide 713 it in the Type 11 SMBIOS table 714 """ 715 pemcert = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), 'OvmfPkKek1.pem') 716 try: 717 with open(pemcert, 'r') as pemfile: 718 key = pemfile.read().replace('\n', ''). \ 719 replace('-----BEGIN CERTIFICATE-----', ''). \ 720 replace('-----END CERTIFICATE-----', '') 721 self.ovmf_secboot_pkkek1 = key 722 723 except FileNotFoundError: 724 raise RunQemuError("Can't open PEM certificate %s " % pemcert) 725 726 def check_ovmf(self): 727 """Check and set full path for OVMF firmware and variable file(s).""" 728 729 for index, ovmf in enumerate(self.ovmf_bios): 730 if os.path.exists(ovmf): 731 continue 732 for suffix in ('qcow2', 'bin'): 733 path = '%s/%s.%s' % (self.get('DEPLOY_DIR_IMAGE'), ovmf, suffix) 734 if os.path.exists(path): 735 self.ovmf_bios[index] = path 736 if ovmf.endswith('secboot'): 737 self.setup_pkkek1() 738 break 739 else: 740 raise RunQemuError("Can't find OVMF firmware: %s" % ovmf) 741 742 def check_kernel(self): 743 """Check and set kernel""" 744 # The vm image doesn't need a kernel 745 if self.fstype in self.vmtypes: 746 return 747 748 # See if the user supplied a KERNEL option 749 if self.get('KERNEL'): 750 self.kernel = self.get('KERNEL') 751 752 # QB_DEFAULT_KERNEL is always a full file path 753 kernel_name = os.path.basename(self.get('QB_DEFAULT_KERNEL')) 754 755 # The user didn't want a kernel to be loaded 756 if kernel_name == "none" and not self.kernel: 757 return 758 759 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 760 if not self.kernel: 761 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) 762 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 763 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) 764 globs = (kernel_match_name, kernel_match_link, kernel_startswith) 765 self.kernel = get_first_file(globs) 766 if not self.kernel: 767 raise RunQemuError('KERNEL not found: %s, %s or %s' % globs) 768 769 if not os.path.exists(self.kernel): 770 raise RunQemuError("KERNEL %s not found" % self.kernel) 771 772 def check_dtb(self): 773 """Check and set dtb""" 774 # Did the user specify a device tree? 775 if self.get('DEVICE_TREE'): 776 self.dtb = self.get('DEVICE_TREE') 777 if not os.path.exists(self.dtb): 778 raise RunQemuError('Specified DTB not found: %s' % self.dtb) 779 return 780 781 dtb = self.get('QB_DTB') 782 if dtb: 783 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 784 glob_match = "%s/%s" % (deploy_dir_image, dtb) 785 glob_startswith = "%s/%s*" % (deploy_dir_image, dtb) 786 glob_wild = "%s/*.dtb" % deploy_dir_image 787 globs = (glob_match, glob_startswith, glob_wild) 788 self.dtb = get_first_file(globs) 789 if not os.path.exists(self.dtb): 790 raise RunQemuError('DTB not found: %s, %s or %s' % globs) 791 792 def check_bios(self): 793 """Check and set bios""" 794 795 # See if the user supplied a BIOS option 796 if self.get('BIOS'): 797 self.bios = self.get('BIOS') 798 799 # QB_DEFAULT_BIOS is always a full file path 800 bios_name = os.path.basename(self.get('QB_DEFAULT_BIOS')) 801 802 # The user didn't want a bios to be loaded 803 if (bios_name == "" or bios_name == "none") and not self.bios: 804 return 805 806 if not self.bios: 807 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 808 self.bios = "%s/%s" % (deploy_dir_image, bios_name) 809 810 if not self.bios: 811 raise RunQemuError('BIOS not found: %s' % bios_match_name) 812 813 if not os.path.exists(self.bios): 814 raise RunQemuError("BIOS %s not found" % self.bios) 815 816 817 def check_mem(self): 818 """ 819 Both qemu and kernel needs memory settings, so check QB_MEM and set it 820 for both. 821 """ 822 s = re.search('-m +([0-9]+)', self.qemuparams) 823 if s: 824 self.set('QB_MEM', '-m %s' % s.group(1)) 825 elif not self.get('QB_MEM'): 826 logger.info('QB_MEM is not set, use 256M by default') 827 self.set('QB_MEM', '-m 256') 828 829 # Check and remove M or m suffix 830 qb_mem = self.get('QB_MEM') 831 if qb_mem.endswith('M') or qb_mem.endswith('m'): 832 qb_mem = qb_mem[:-1] 833 834 # Add -m prefix it not present 835 if not qb_mem.startswith('-m'): 836 qb_mem = '-m %s' % qb_mem 837 838 self.set('QB_MEM', qb_mem) 839 840 mach = self.get('MACHINE') 841 if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')): 842 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' 843 844 self.qemu_opt_script += ' %s' % self.get('QB_MEM') 845 846 def check_tcpserial(self): 847 if self.tcpserial_portnum: 848 ports = self.tcpserial_portnum.split(':') 849 port = ports[0] 850 if self.get('QB_TCPSERIAL_OPT'): 851 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) 852 else: 853 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port 854 855 if len(ports) > 1: 856 for port in ports[1:]: 857 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port 858 859 def check_and_set(self): 860 """Check configs sanity and set when needed""" 861 self.validate_paths() 862 if not self.slirp_enabled and not self.net_bridge: 863 check_tun() 864 # Check audio 865 if self.audio_enabled: 866 if not self.get('QB_AUDIO_DRV'): 867 raise RunQemuError("QB_AUDIO_DRV is NULL, this board doesn't support audio") 868 if not self.get('QB_AUDIO_OPT'): 869 logger.warning('QB_AUDIO_OPT is NULL, you may need define it to make audio work') 870 else: 871 self.qemu_opt_script += ' %s' % self.get('QB_AUDIO_OPT') 872 os.putenv('QEMU_AUDIO_DRV', self.get('QB_AUDIO_DRV')) 873 else: 874 os.putenv('QEMU_AUDIO_DRV', 'none') 875 876 self.check_qemu_system() 877 self.check_kvm() 878 self.check_fstype() 879 self.check_rootfs() 880 self.check_ovmf() 881 self.check_kernel() 882 self.check_dtb() 883 self.check_bios() 884 self.check_mem() 885 self.check_tcpserial() 886 887 def read_qemuboot(self): 888 if not self.qemuboot: 889 if self.get('DEPLOY_DIR_IMAGE'): 890 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') 891 else: 892 logger.warning("Can't find qemuboot conf file, DEPLOY_DIR_IMAGE is NULL!") 893 return 894 895 if self.rootfs and not os.path.exists(self.rootfs): 896 # Lazy rootfs 897 machine = self.get('MACHINE') 898 if not machine: 899 machine = os.path.basename(deploy_dir_image) 900 if not self.get('IMAGE_LINK_NAME'): 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')) 904 else: 905 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image 906 logger.debug('Running %s...' % cmd) 907 try: 908 qbs = subprocess.check_output(cmd, shell=True).decode('utf-8') 909 except subprocess.CalledProcessError as err: 910 raise RunQemuError(err) 911 if qbs: 912 for qb in qbs.split(): 913 # Don't use initramfs when other choices unless fstype is ramfs 914 if '-initramfs-' in os.path.basename(qb) and self.fstype != 'cpio.gz': 915 continue 916 self.qemuboot = qb 917 break 918 if not self.qemuboot: 919 # Use the first one when no choice 920 self.qemuboot = qbs.split()[0] 921 self.qbconfload = True 922 923 if not self.qemuboot: 924 # If we haven't found a .qemuboot.conf at this point it probably 925 # doesn't exist, continue without 926 return 927 928 if not os.path.exists(self.qemuboot): 929 raise RunQemuError("Failed to find %s (wrong image name or BSP does not support running under qemu?)." % self.qemuboot) 930 931 logger.debug('CONFFILE: %s' % self.qemuboot) 932 933 cf = configparser.ConfigParser() 934 cf.read(self.qemuboot) 935 for k, v in cf.items('config_bsp'): 936 k_upper = k.upper() 937 if v.startswith("../"): 938 v = os.path.abspath(os.path.dirname(self.qemuboot) + "/" + v) 939 elif v == ".": 940 v = os.path.dirname(self.qemuboot) 941 self.set(k_upper, v) 942 943 def validate_paths(self): 944 """Ensure all relevant path variables are set""" 945 # When we're started with a *.qemuboot.conf arg assume that image 946 # artefacts are relative to that file, rather than in whatever 947 # directory DEPLOY_DIR_IMAGE in the conf file points to. 948 if self.qbconfload: 949 imgdir = os.path.realpath(os.path.dirname(self.qemuboot)) 950 if imgdir != os.path.realpath(self.get('DEPLOY_DIR_IMAGE')): 951 logger.info('Setting DEPLOY_DIR_IMAGE to folder containing %s (%s)' % (self.qemuboot, imgdir)) 952 self.set('DEPLOY_DIR_IMAGE', imgdir) 953 954 # If the STAGING_*_NATIVE directories from the config file don't exist 955 # and we're in a sourced OE build directory try to extract the paths 956 # from `bitbake -e` 957 havenative = os.path.exists(self.get('STAGING_DIR_NATIVE')) and \ 958 os.path.exists(self.get('STAGING_BINDIR_NATIVE')) 959 960 if not havenative: 961 if not self.bitbake_e: 962 self.load_bitbake_env() 963 964 if self.bitbake_e: 965 native_vars = ['STAGING_DIR_NATIVE'] 966 for nv in native_vars: 967 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M) 968 if s and s.group(1) != self.get(nv): 969 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1))) 970 self.set(nv, s.group(1)) 971 else: 972 # when we're invoked from a running bitbake instance we won't 973 # be able to call `bitbake -e`, then try: 974 # - get OE_TMPDIR from environment and guess paths based on it 975 # - get OECORE_NATIVE_SYSROOT from environment (for sdk) 976 tmpdir = self.get('OE_TMPDIR') 977 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT') 978 if tmpdir: 979 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir) 980 hostos, _, _, _, machine = os.uname() 981 buildsys = '%s-%s' % (machine, hostos.lower()) 982 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys) 983 self.set('STAGING_DIR_NATIVE', staging_dir_native) 984 elif oecore_native_sysroot: 985 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot) 986 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot) 987 if self.get('STAGING_DIR_NATIVE'): 988 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin 989 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE') 990 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native) 991 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')) 992 993 def print_config(self): 994 logoutput = ['Continuing with the following parameters:'] 995 if not self.fstype in self.vmtypes: 996 logoutput.append('KERNEL: [%s]' % self.kernel) 997 if self.bios: 998 logoutput.append('BIOS: [%s]' % self.bios) 999 if self.dtb: 1000 logoutput.append('DTB: [%s]' % self.dtb) 1001 logoutput.append('MACHINE: [%s]' % self.get('MACHINE')) 1002 try: 1003 fstype_flags = ' (' + ', '.join(self.fsinfo[self.fstype]) + ')' 1004 except KeyError: 1005 fstype_flags = '' 1006 logoutput.append('FSTYPE: [%s%s]' % (self.fstype, fstype_flags)) 1007 if self.fstype == 'nfs': 1008 logoutput.append('NFS_DIR: [%s]' % self.rootfs) 1009 else: 1010 logoutput.append('ROOTFS: [%s]' % self.rootfs) 1011 if self.ovmf_bios: 1012 logoutput.append('OVMF: %s' % self.ovmf_bios) 1013 if (self.ovmf_secboot_pkkek1): 1014 logoutput.append('SECBOOT PKKEK1: [%s...]' % self.ovmf_secboot_pkkek1[0:100]) 1015 logoutput.append('CONFFILE: [%s]' % self.qemuboot) 1016 logoutput.append('') 1017 logger.info('\n'.join(logoutput)) 1018 1019 def setup_nfs(self): 1020 if not self.nfs_server: 1021 if self.slirp_enabled: 1022 self.nfs_server = '10.0.2.2' 1023 else: 1024 self.nfs_server = '192.168.7.@GATEWAY@' 1025 1026 nfsd_port = 3048 + self.nfs_instance 1027 lockdir = "/tmp/qemu-port-locks" 1028 self.make_lock_dir(lockdir) 1029 while not self.check_free_port('localhost', nfsd_port, lockdir): 1030 self.nfs_instance += 1 1031 nfsd_port += 1 1032 1033 mountd_port = nfsd_port 1034 # Export vars for runqemu-export-rootfs 1035 export_dict = { 1036 'NFS_INSTANCE': self.nfs_instance, 1037 'NFSD_PORT': nfsd_port, 1038 'MOUNTD_PORT': mountd_port, 1039 } 1040 for k, v in export_dict.items(): 1041 # Use '%s' since they are integers 1042 os.putenv(k, '%s' % v) 1043 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) 1049 1050 # Extract .tar.bz2 or .tar.bz if no nfs dir 1051 if not (self.rootfs and os.path.isdir(self.rootfs)): 1052 src_prefix = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME')) 1053 dest = "%s-nfsroot" % src_prefix 1054 if os.path.exists('%s.pseudo_state' % dest): 1055 logger.info('Use %s as NFS_DIR' % dest) 1056 self.rootfs = dest 1057 else: 1058 src = "" 1059 src1 = '%s.tar.bz2' % src_prefix 1060 src2 = '%s.tar.gz' % src_prefix 1061 if os.path.exists(src1): 1062 src = src1 1063 elif os.path.exists(src2): 1064 src = src2 1065 if not src: 1066 raise RunQemuError("No NFS_DIR is set, and can't find %s or %s to extract" % (src1, src2)) 1067 logger.info('NFS_DIR not found, extracting %s to %s' % (src, dest)) 1068 cmd = ('runqemu-extract-sdk', src, dest) 1069 logger.info('Running %s...' % str(cmd)) 1070 if subprocess.call(cmd) != 0: 1071 raise RunQemuError('Failed to run %s' % str(cmd)) 1072 self.rootfs = dest 1073 self.cleanup_files.append(self.rootfs) 1074 self.cleanup_files.append('%s.pseudo_state' % self.rootfs) 1075 1076 # Start the userspace NFS server 1077 cmd = ('runqemu-export-rootfs', 'start', self.rootfs) 1078 logger.info('Running %s...' % str(cmd)) 1079 if subprocess.call(cmd) != 0: 1080 raise RunQemuError('Failed to run %s' % str(cmd)) 1081 1082 self.nfs_running = True 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 1091 def setup_net_bridge(self): 1092 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( 1093 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) 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 1106 def setup_slirp(self): 1107 """Setup user networking""" 1108 1109 if self.fstype == 'nfs': 1110 self.setup_nfs() 1111 netconf = " " + self.cmdline_ip_slirp 1112 logger.info("Network configuration:%s", netconf) 1113 self.kernel_cmdline_script += netconf 1114 # Port mapping 1115 hostfwd = ",hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23" 1116 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) 1117 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default 1118 # Figure out the port 1119 ports = re.findall('hostfwd=[^-]*:([0-9]+)-[^,-]*', qb_slirp_opt) 1120 ports = [int(i) for i in ports] 1121 mac = 2 1122 1123 lockdir = "/tmp/qemu-port-locks" 1124 self.make_lock_dir(lockdir) 1125 1126 # Find a free port to avoid conflicts 1127 for p in ports[:]: 1128 p_new = p 1129 while not self.check_free_port('localhost', p_new, lockdir): 1130 p_new += 1 1131 mac += 1 1132 while p_new in ports: 1133 p_new += 1 1134 mac += 1 1135 if p != p_new: 1136 ports.append(p_new) 1137 qb_slirp_opt = re.sub(':%s-' % p, ':%s-' % p_new, qb_slirp_opt) 1138 logger.info("Port forward changed: %s -> %s" % (p, p_new)) 1139 mac = "%s%02x" % (self.mac_slirp, mac) 1140 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qb_slirp_opt)) 1141 # Print out port foward 1142 hostfwd = re.findall('(hostfwd=[^,]*)', qb_slirp_opt) 1143 if hostfwd: 1144 logger.info('Port forward: %s' % ' '.join(hostfwd)) 1145 1146 def setup_tap(self): 1147 """Setup tap""" 1148 1149 # This file is created when runqemu-gen-tapdevs creates a bank of tap 1150 # devices, indicating that the user should not bring up new ones using 1151 # sudo. 1152 nosudo_flag = '/etc/runqemu-nosudo' 1153 self.qemuifup = shutil.which('runqemu-ifup') 1154 self.qemuifdown = shutil.which('runqemu-ifdown') 1155 ip = shutil.which('ip') 1156 lockdir = "/tmp/qemu-tap-locks" 1157 1158 if not (self.qemuifup and self.qemuifdown and ip): 1159 logger.error("runqemu-ifup: %s" % self.qemuifup) 1160 logger.error("runqemu-ifdown: %s" % self.qemuifdown) 1161 logger.error("ip: %s" % ip) 1162 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") 1163 1164 self.make_lock_dir(lockdir) 1165 1166 cmd = (ip, 'link') 1167 logger.debug('Running %s...' % str(cmd)) 1168 ip_link = subprocess.check_output(cmd).decode('utf-8') 1169 # Matches line like: 6: tap0: <foo> 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) 1175 tap = "" 1176 for p in possibles: 1177 lockfile = os.path.join(lockdir, p) 1178 if os.path.exists('%s.skip' % lockfile): 1179 logger.info('Found %s.skip, skipping %s' % (lockfile, p)) 1180 continue 1181 self.taplock = lockfile + '.lock' 1182 if self.acquire_taplock(error=False): 1183 tap = p 1184 logger.info("Using preconfigured tap device %s" % tap) 1185 logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap)) 1186 break 1187 1188 if not tap: 1189 if os.path.exists(nosudo_flag): 1190 logger.error("Error: There are no available tap devices to use for networking,") 1191 logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag) 1192 raise RunQemuError("a new one with sudo.") 1193 1194 gid = os.getgid() 1195 uid = os.getuid() 1196 logger.info("Setting up tap interface under sudo") 1197 cmd = ('sudo', self.qemuifup, str(gid)) 1198 try: 1199 tap = subprocess.check_output(cmd).decode('utf-8').strip() 1200 except subprocess.CalledProcessError as e: 1201 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e)) 1202 sys.exit(1) 1203 lockfile = os.path.join(lockdir, tap) 1204 self.taplock = lockfile + '.lock' 1205 self.acquire_taplock() 1206 self.cleantap = True 1207 logger.debug('Created tap: %s' % tap) 1208 1209 if not tap: 1210 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") 1211 sys.exit(1) 1212 self.tap = tap 1213 tapnum = int(tap[len(oe_tap_name):]) 1214 gateway = tapnum * 2 + 1 1215 client = gateway + 1 1216 if self.fstype == 'nfs': 1217 self.setup_nfs() 1218 netconf = " " + self.cmdline_ip_tap 1219 netconf = netconf.replace('@CLIENT@', str(client)) 1220 netconf = netconf.replace('@GATEWAY@', str(gateway)) 1221 self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway)) 1222 logger.info("Network configuration:%s", netconf) 1223 self.kernel_cmdline_script += netconf 1224 mac = "%s%02x" % (self.mac_tap, client) 1225 qb_tap_opt = self.get('QB_TAP_OPT') 1226 if qb_tap_opt: 1227 qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap) 1228 else: 1229 qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap) 1230 1231 if self.vhost_enabled: 1232 qemu_tap_opt += ',vhost=on' 1233 1234 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) 1235 1236 def setup_network(self): 1237 if self.nonetwork or self.get('QB_NET') == 'none': 1238 self.set('NETWORK_CMD', '-nic none') 1239 return 1240 if sys.stdin.isatty(): 1241 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() 1242 self.network_device = self.get('QB_NETWORK_DEVICE') or self.network_device 1243 if self.net_bridge: 1244 self.setup_net_bridge() 1245 elif self.slirp_enabled: 1246 self.cmdline_ip_slirp = self.get('QB_CMDLINE_IP_SLIRP') or self.cmdline_ip_slirp 1247 self.setup_slirp() 1248 else: 1249 self.cmdline_ip_tap = self.get('QB_CMDLINE_IP_TAP') or self.cmdline_ip_tap 1250 self.setup_tap() 1251 1252 def setup_rootfs(self): 1253 if self.get('QB_ROOTFS') == 'none': 1254 return 1255 if 'wic.' in self.fstype: 1256 self.fstype = self.fstype[4:] 1257 rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' 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 1271 qb_rootfs_opt = self.get('QB_ROOTFS_OPT') 1272 if qb_rootfs_opt: 1273 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) 1274 else: 1275 self.rootfs_options = '-drive file=%s,if=virtio,format=%s' % (self.rootfs, rootfs_format) 1276 1277 qb_rootfs_extra_opt = self.get("QB_ROOTFS_EXTRA_OPT") 1278 if qb_rootfs_extra_opt and not qb_rootfs_extra_opt.startswith(","): 1279 qb_rootfs_extra_opt = "," + qb_rootfs_extra_opt 1280 1281 if self.fstype in ('cpio.gz', 'cpio'): 1282 self.kernel_cmdline = 'root=/dev/ram0 rw debugshell' 1283 self.rootfs_options = '-initrd %s' % self.rootfs 1284 else: 1285 vm_drive = '' 1286 if self.fstype in self.vmtypes: 1287 if self.fstype == 'iso': 1288 vm_drive = '-drive file=%s,if=virtio,media=cdrom' % self.rootfs 1289 elif self.get('QB_DRIVE_TYPE'): 1290 drive_type = self.get('QB_DRIVE_TYPE') 1291 if drive_type.startswith("/dev/sd"): 1292 logger.info('Using scsi drive') 1293 vm_drive = '-drive if=none,id=hd,file=%s,format=%s -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd%s' \ 1294 % (self.rootfs, rootfs_format, qb_rootfs_extra_opt) 1295 elif drive_type.startswith("/dev/hd"): 1296 logger.info('Using ide drive') 1297 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format) 1298 elif drive_type.startswith("/dev/vdb"): 1299 logger.info('Using block virtio drive'); 1300 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \ 1301 % (self.rootfs, rootfs_format,qb_rootfs_extra_opt) 1302 else: 1303 # virtio might have been selected explicitly (just use it), or 1304 # is used as fallback (then warn about that). 1305 if not drive_type.startswith("/dev/vd"): 1306 logger.warning("Unknown QB_DRIVE_TYPE: %s" % drive_type) 1307 logger.warning("Failed to figure out drive type, consider define or fix QB_DRIVE_TYPE") 1308 logger.warning('Trying to use virtio block drive') 1309 vm_drive = '-drive if=virtio,file=%s,format=%s' % (self.rootfs, rootfs_format) 1310 1311 # All branches above set vm_drive. 1312 self.rootfs_options = vm_drive 1313 if not self.fstype in self.vmtypes: 1314 self.rootfs_options += ' -no-reboot' 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 1322 1323 if self.fstype == 'nfs': 1324 self.rootfs_options = '' 1325 k_root = '/dev/nfs nfsroot=%s:%s,%s' % (self.nfs_server, os.path.abspath(self.rootfs), self.unfs_opts) 1326 self.kernel_cmdline = 'root=%s rw' % k_root 1327 1328 if self.fstype == 'none': 1329 self.rootfs_options = '' 1330 1331 self.set('ROOTFS_OPTIONS', self.rootfs_options) 1332 1333 def guess_qb_system(self): 1334 """attempt to determine the appropriate qemu-system binary""" 1335 mach = self.get('MACHINE') 1336 if not mach: 1337 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' 1338 if self.rootfs: 1339 match = re.match(search, self.rootfs) 1340 if match: 1341 mach = match.group(1) 1342 elif self.kernel: 1343 match = re.match(search, self.kernel) 1344 if match: 1345 mach = match.group(1) 1346 1347 if not mach: 1348 return None 1349 1350 if mach == 'qemuarm': 1351 qbsys = 'arm' 1352 elif mach == 'qemuarm64': 1353 qbsys = 'aarch64' 1354 elif mach == 'qemux86': 1355 qbsys = 'i386' 1356 elif mach == 'qemux86-64': 1357 qbsys = 'x86_64' 1358 elif mach == 'qemuppc': 1359 qbsys = 'ppc' 1360 elif mach == 'qemuloongarch64': 1361 qbsys = 'loongarch64' 1362 elif mach == 'qemumips': 1363 qbsys = 'mips' 1364 elif mach == 'qemumips64': 1365 qbsys = 'mips64' 1366 elif mach == 'qemumipsel': 1367 qbsys = 'mipsel' 1368 elif mach == 'qemumips64el': 1369 qbsys = 'mips64el' 1370 elif mach == 'qemuriscv64': 1371 qbsys = 'riscv64' 1372 elif mach == 'qemuriscv32': 1373 qbsys = 'riscv32' 1374 else: 1375 logger.error("Unable to determine QEMU PC System emulator for %s machine." % mach) 1376 logger.error("As %s is not among valid QEMU machines such as," % mach) 1377 logger.error("qemux86-64, qemux86, qemuarm64, qemuarm, qemumips64, qemumips64el, qemumipsel, qemumips, qemuppc") 1378 raise RunQemuError("Set qb_system_name with suitable QEMU PC System emulator in .*qemuboot.conf.") 1379 1380 return 'qemu-system-%s' % qbsys 1381 1382 def check_qemu_system(self): 1383 qemu_system = self.get('QB_SYSTEM_NAME') 1384 if not qemu_system: 1385 qemu_system = self.guess_qb_system() 1386 if not qemu_system: 1387 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") 1388 self.qemu_system = qemu_system 1389 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): 1511 qemu_bin = os.path.join(self.bindir_native, self.qemu_system) 1512 1513 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't 1514 # find QEMU in sysroot, it needs to use host's qemu. 1515 if not os.path.exists(qemu_bin): 1516 logger.info("QEMU binary not found in %s, trying host's QEMU" % qemu_bin) 1517 for path in (os.environ['PATH'] or '').split(':'): 1518 qemu_bin_tmp = os.path.join(path, self.qemu_system) 1519 logger.info("Trying: %s" % qemu_bin_tmp) 1520 if os.path.exists(qemu_bin_tmp): 1521 qemu_bin = qemu_bin_tmp 1522 if not os.path.isabs(qemu_bin): 1523 qemu_bin = os.path.abspath(qemu_bin) 1524 logger.info("Using host's QEMU: %s" % qemu_bin) 1525 break 1526 1527 if not os.access(qemu_bin, os.X_OK): 1528 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) 1529 self.qemu_bin = qemu_bin 1530 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'))) 1536 1537 for ovmf in self.ovmf_bios: 1538 format = ovmf.rsplit('.', 1)[-1] 1539 if format == "bin": 1540 format = "raw" 1541 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) 1542 1543 self.qemu_opt += ' ' + self.qemu_opt_script 1544 1545 if self.ovmf_secboot_pkkek1: 1546 # Provide the Platform Key and first Key Exchange Key certificate as an 1547 # OEM string in the SMBIOS Type 11 table. Prepend the certificate string 1548 # with "application prefix" of the EnrollDefaultKeys.efi application 1549 self.qemu_opt += ' -smbios type=11,value=4e32566d-8e9e-4f52-81d3-5bb9715f9727:' \ 1550 + self.ovmf_secboot_pkkek1 1551 1552 # Append qemuparams to override previous settings 1553 if self.qemuparams: 1554 self.qemu_opt += ' ' + self.qemuparams 1555 1556 if self.snapshot: 1557 self.qemu_opt += " -snapshot" 1558 1559 self.setup_guest_agent() 1560 self.setup_qmp() 1561 self.setup_serial() 1562 self.setup_vga() 1563 1564 def start_qemu(self): 1565 import shlex 1566 if self.kernel: 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, 1573 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), 1574 self.bootparams) 1575 if self.dtb: 1576 kernel_opts += " -dtb %s" % self.dtb 1577 else: 1578 kernel_opts = "" 1579 1580 if self.bios: 1581 self.qemu_opt += " -bios %s" % self.bios 1582 1583 cmd = "%s %s" % (self.qemu_opt, kernel_opts) 1584 cmds = shlex.split(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) 1589 pass_fds = [] 1590 if self.taplock_descriptor: 1591 pass_fds = [self.taplock_descriptor.fileno()] 1592 if len(self.portlocks): 1593 for descriptor in self.portlocks.values(): 1594 pass_fds.append(descriptor.fileno()) 1595 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ) 1596 self.qemuprocess = process 1597 retcode = process.wait() 1598 if retcode: 1599 if retcode == -signal.SIGTERM: 1600 logger.info("Qemu terminated by SIGTERM") 1601 else: 1602 logger.error("Failed to run qemu: %s", process.stderr.read().decode()) 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 1611 def cleanup(self): 1612 if self.cleaned: 1613 return 1614 1615 # avoid dealing with SIGTERM when cleanup function is running 1616 signal.signal(signal.SIGTERM, signal.SIG_IGN) 1617 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) 1631 if self.cleantap: 1632 cmd = ('sudo', self.qemuifdown, self.tap) 1633 logger.debug('Running %s' % str(cmd)) 1634 subprocess.check_call(cmd) 1635 self.release_taplock() 1636 1637 if self.nfs_running: 1638 logger.info("Shutting down the userspace NFS server...") 1639 cmd = ("runqemu-export-rootfs", "stop", self.rootfs) 1640 logger.debug('Running %s' % str(cmd)) 1641 subprocess.check_call(cmd) 1642 self.release_portlock() 1643 1644 if self.saved_stty: 1645 subprocess.check_call(("stty", self.saved_stty)) 1646 1647 if self.cleanup_files: 1648 for ent in self.cleanup_files: 1649 logger.info('Removing %s' % ent) 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"]) 1657 1658 self.cleaned = True 1659 1660 def run_bitbake_env(self, mach=None, target=''): 1661 bitbake = shutil.which('bitbake') 1662 if not bitbake: 1663 return 1664 1665 if not mach: 1666 mach = self.get('MACHINE') 1667 1668 multiconfig = self.get('MULTICONFIG') 1669 if multiconfig: 1670 multiconfig = "mc:%s" % multiconfig 1671 1672 if mach: 1673 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) 1674 else: 1675 cmd = 'bitbake -e %s %s' % (multiconfig, target) 1676 1677 logger.info('Running %s...' % cmd) 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 1694 1695 def load_bitbake_env(self, mach=None, target=None): 1696 if self.bitbake_e: 1697 return 1698 1699 self.bitbake_e = self.run_bitbake_env(mach=mach, target=target) 1700 1701 def validate_combos(self): 1702 if (self.fstype in self.vmtypes) and self.kernel: 1703 raise RunQemuError("%s doesn't need kernel %s!" % (self.fstype, self.kernel)) 1704 1705 @property 1706 def bindir_native(self): 1707 result = self.get('STAGING_BINDIR_NATIVE') 1708 if result and os.path.exists(result): 1709 return result 1710 1711 cmd = ['bitbake', '-e'] 1712 multiconfig = self.get('MULTICONFIG') 1713 if multiconfig: 1714 cmd.append('mc:%s:qemu-helper-native' % multiconfig) 1715 else: 1716 cmd.append('qemu-helper-native') 1717 1718 logger.info('Running %s...' % str(cmd)) 1719 out = subprocess.check_output(cmd).decode('utf-8') 1720 1721 match = re.search('^STAGING_BINDIR_NATIVE="(.*)"', out, re.M) 1722 if match: 1723 result = match.group(1) 1724 if os.path.exists(result): 1725 self.set('STAGING_BINDIR_NATIVE', result) 1726 return result 1727 raise RunQemuError("Native sysroot directory %s doesn't exist" % result) 1728 else: 1729 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd)) 1730 1731 1732def main(): 1733 if "help" in sys.argv or '-h' in sys.argv or '--help' in sys.argv: 1734 print_usage() 1735 return 0 1736 try: 1737 config = BaseConfig() 1738 1739 renice = os.path.expanduser("~/bin/runqemu-renice") 1740 if os.path.exists(renice): 1741 logger.info('Using %s to renice' % renice) 1742 subprocess.check_call([renice, str(os.getpid())]) 1743 1744 def sigterm_handler(signum, frame): 1745 logger.info("Received signal: %s" % (signum)) 1746 config.cleanup() 1747 signal.signal(signal.SIGTERM, sigterm_handler) 1748 1749 config.check_args() 1750 config.read_qemuboot() 1751 config.check_and_set() 1752 # Check whether the combos is valid or not 1753 config.validate_combos() 1754 config.print_config() 1755 config.setup_network() 1756 config.setup_rootfs() 1757 config.setup_final() 1758 config.setup_cmd() 1759 config.start_qemu() 1760 except RunQemuError as err: 1761 logger.error(err) 1762 return 1 1763 except Exception as err: 1764 import traceback 1765 traceback.print_exc() 1766 return 1 1767 finally: 1768 config.cleanup_cmd() 1769 config.cleanup() 1770 1771if __name__ == "__main__": 1772 sys.exit(main()) 1773