xref: /openbmc/openbmc/poky/scripts/runqemu (revision 169d7bcc)
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