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