summaryrefslogtreecommitdiffstats
path: root/scripts/runqemu
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-xscripts/runqemu1785
1 files changed, 0 insertions, 1785 deletions
diff --git a/scripts/runqemu b/scripts/runqemu
deleted file mode 100755
index 3d77046972..0000000000
--- a/scripts/runqemu
+++ /dev/null
@@ -1,1785 +0,0 @@
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_mesa_paths(self):
472 drivers_path = os.path.join(self.bindir_native, '../lib/dri')
473 gbm_path = os.path.join(self.bindir_native, '../lib/gbm')
474 if not os.path.exists(drivers_path) or not os.listdir(drivers_path) \
475 or not os.path.exists(gbm_path) or not os.listdir(gbm_path):
476 raise RunQemuError("""
477qemu has been built without opengl support and accelerated graphics support is not available.
478To enable it, add:
479DISTRO_FEATURES_NATIVE:append = " opengl"
480DISTRO_FEATURES_NATIVESDK:append = " opengl"
481to your build configuration.
482""")
483 self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path
484 self.qemu_environ['GBM_BACKENDS_PATH'] = gbm_path
485
486 def check_args(self):
487 for debug in ("-d", "--debug"):
488 if debug in sys.argv:
489 logger.setLevel(logging.DEBUG)
490 sys.argv.remove(debug)
491
492 for quiet in ("-q", "--quiet"):
493 if quiet in sys.argv:
494 logger.setLevel(logging.ERROR)
495 sys.argv.remove(quiet)
496
497 if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]:
498 self.qemu_environ['SDL_RENDER_DRIVER'] = 'software'
499 self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false'
500
501 unknown_arg = ""
502 for arg in sys.argv[1:]:
503 if arg in self.fstypes + self.vmtypes + self.wictypes:
504 self.check_arg_fstype(arg)
505 elif arg == 'nographic':
506 self.nographic = True
507 elif arg == "nonetwork":
508 self.nonetwork = True
509 elif arg == 'sdl':
510 self.sdl = True
511 elif arg == 'gtk':
512 self.gtk = True
513 elif arg == 'gl':
514 self.gl = True
515 elif arg == 'gl-es':
516 self.gl_es = True
517 elif arg == 'egl-headless':
518 self.egl_headless = True
519 elif arg == 'novga':
520 self.novga = True
521 elif arg == 'serial':
522 self.serialconsole = True
523 elif arg == "serialstdio":
524 self.serialstdio = True
525 elif arg == 'audio':
526 logger.info("Enabling audio in qemu")
527 logger.info("Please install sound drivers in linux host")
528 self.audio_enabled = True
529 elif arg == 'kvm':
530 self.kvm_enabled = True
531 elif arg == 'kvm-vhost':
532 self.vhost_enabled = True
533 elif arg == 'slirp':
534 self.slirp_enabled = True
535 elif arg.startswith('bridge='):
536 self.net_bridge = '%s' % arg[len('bridge='):]
537 elif arg == 'snapshot':
538 self.snapshot = True
539 elif arg == 'publicvnc':
540 self.publicvnc = True
541 self.qemu_opt_script += ' -vnc :0'
542 elif arg == 'guestagent':
543 self.guest_agent = True
544 elif arg == "qmp":
545 self.qmp = "unix:qmp.sock"
546 elif arg.startswith("qmp="):
547 self.qmp = arg[len('qmp='):]
548 elif arg.startswith('guestagent-sockpath='):
549 self.guest_agent_sockpath = '%s' % arg[len('guestagent-sockpath='):]
550 elif arg.startswith('tcpserial='):
551 self.tcpserial_portnum = '%s' % arg[len('tcpserial='):]
552 elif arg.startswith('qemuparams='):
553 self.qemuparams = ' %s' % arg[len('qemuparams='):]
554 elif arg.startswith('bootparams='):
555 self.bootparams = arg[len('bootparams='):]
556 elif os.path.exists(arg) or (re.search(':', arg) and re.search('/', arg)):
557 self.check_arg_path(os.path.abspath(arg))
558 elif re.search(r'-image-|-image$', arg):
559 # Lazy rootfs
560 self.rootfs = arg
561 elif arg.startswith('ovmf'):
562 self.ovmf_bios.append(arg)
563 else:
564 # At last, assume it is the MACHINE
565 if (not unknown_arg) or unknown_arg == arg:
566 unknown_arg = arg
567 else:
568 raise RunQemuError("Can't handle two unknown args: %s %s\n"
569 "Try 'runqemu help' on how to use it" % \
570 (unknown_arg, arg))
571 # Check to make sure it is a valid machine
572 if unknown_arg and self.get('MACHINE') != unknown_arg:
573 if self.get('DEPLOY_DIR_IMAGE'):
574 machine = os.path.basename(self.get('DEPLOY_DIR_IMAGE'))
575 if unknown_arg == machine:
576 self.set("MACHINE", machine)
577
578 self.check_arg_machine(unknown_arg)
579
580 if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload):
581 self.load_bitbake_env(target=self.rootfs)
582 s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M)
583 if s:
584 self.set("DEPLOY_DIR_IMAGE", s.group(1))
585
586 if not self.get('IMAGE_LINK_NAME') and self.rootfs:
587 s = re.search('^IMAGE_LINK_NAME="(.*)"', self.bitbake_e, re.M)
588 if s:
589 image_link_name = s.group(1)
590 self.set("IMAGE_LINK_NAME", image_link_name)
591 logger.debug('Using IMAGE_LINK_NAME = "%s"' % image_link_name)
592
593 def check_kvm(self):
594 """Check kvm and kvm-host"""
595 if not (self.kvm_enabled or self.vhost_enabled):
596 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP'))
597 return
598
599 if not self.get('QB_CPU_KVM'):
600 raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm")
601
602 self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP'))
603 yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu"
604 yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM"
605 dev_kvm = '/dev/kvm'
606 dev_vhost = '/dev/vhost-net'
607 if self.qemu_system.endswith(('i386', 'x86_64')):
608 with open('/proc/cpuinfo', 'r') as f:
609 kvm_cap = re.search('vmx|svm', "".join(f.readlines()))
610 if not kvm_cap:
611 logger.error("You are trying to enable KVM on a cpu without VT support.")
612 logger.error("Remove kvm from the command-line, or refer:")
613 raise RunQemuError(yocto_kvm_wiki)
614
615 if not os.path.exists(dev_kvm):
616 logger.error("Missing KVM device. Have you inserted kvm modules?")
617 logger.error("For further help see:")
618 raise RunQemuError(yocto_kvm_wiki)
619
620 if os.access(dev_kvm, os.W_OK|os.R_OK):
621 self.qemu_opt_script += ' -enable-kvm'
622 else:
623 logger.error("You have no read or write permission on /dev/kvm.")
624 logger.error("Please change the ownership of this file as described at:")
625 raise RunQemuError(yocto_kvm_wiki)
626
627 if self.vhost_enabled:
628 if not os.path.exists(dev_vhost):
629 logger.error("Missing virtio net device. Have you inserted vhost-net module?")
630 logger.error("For further help see:")
631 raise RunQemuError(yocto_paravirt_kvm_wiki)
632
633 if not os.access(dev_vhost, os.W_OK|os.R_OK):
634 logger.error("You have no read or write permission on /dev/vhost-net.")
635 logger.error("Please change the ownership of this file as described at:")
636 raise RunQemuError(yocto_paravirt_kvm_wiki)
637
638 def check_fstype(self):
639 """Check and setup FSTYPE"""
640 if not self.fstype:
641 fstype = self.get('QB_DEFAULT_FSTYPE')
642 if fstype:
643 self.fstype = fstype
644 else:
645 raise RunQemuError("FSTYPE is NULL!")
646
647 # parse QB_FSINFO into dict, e.g. { 'wic': ['no-kernel-in-fs', 'a-flag'], 'ext4': ['another-flag']}
648 wic_fs = False
649 qb_fsinfo = self.get('QB_FSINFO')
650 if qb_fsinfo:
651 qb_fsinfo = qb_fsinfo.split()
652 for fsinfo in qb_fsinfo:
653 try:
654 fstype, fsflag = fsinfo.split(':')
655
656 if fstype == 'wic':
657 if fsflag == 'no-kernel-in-fs':
658 wic_fs = True
659 elif fsflag == 'kernel-in-fs':
660 wic_fs = False
661 else:
662 logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag)
663 continue
664 else:
665 logger.warning('QB_FSINFO is not supported for image type "%s"', fstype)
666 continue
667
668 if fstype in self.fsinfo:
669 self.fsinfo[fstype].append(fsflag)
670 else:
671 self.fsinfo[fstype] = [fsflag]
672 except Exception:
673 logger.error('Invalid parameter "%s" in QB_FSINFO', fsinfo)
674
675 # treat wic images as vmimages (with kernel) or as fsimages (rootfs only)
676 if wic_fs:
677 self.fstypes = self.fstypes + self.wictypes
678 else:
679 self.vmtypes = self.vmtypes + self.wictypes
680
681 def check_rootfs(self):
682 """Check and set rootfs"""
683
684 if self.fstype == "none":
685 return
686
687 if self.get('ROOTFS'):
688 if not self.rootfs:
689 self.rootfs = self.get('ROOTFS')
690 elif self.get('ROOTFS') != self.rootfs:
691 raise RunQemuError("Maybe conflicted ROOTFS: %s vs %s" % (self.get('ROOTFS'), self.rootfs))
692
693 if self.fstype == 'nfs':
694 return
695
696 if self.rootfs and not os.path.exists(self.rootfs):
697 # Lazy rootfs
698 self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'),
699 self.get('IMAGE_LINK_NAME'),
700 self.fstype)
701 elif not self.rootfs:
702 glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype)
703 glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype)
704 globs = (glob_name, glob_link)
705 self.rootfs = get_first_file(globs)
706 if not self.rootfs:
707 raise RunQemuError("Failed to find rootfs: %s or %s" % globs)
708
709 if not os.path.exists(self.rootfs):
710 raise RunQemuError("Can't find rootfs: %s" % self.rootfs)
711
712 def setup_pkkek1(self):
713 """
714 Extract from PEM certificate the Platform Key and first Key
715 Exchange Key certificate string. The hypervisor needs to provide
716 it in the Type 11 SMBIOS table
717 """
718 pemcert = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), 'OvmfPkKek1.pem')
719 try:
720 with open(pemcert, 'r') as pemfile:
721 key = pemfile.read().replace('\n', ''). \
722 replace('-----BEGIN CERTIFICATE-----', ''). \
723 replace('-----END CERTIFICATE-----', '')
724 self.ovmf_secboot_pkkek1 = key
725
726 except FileNotFoundError:
727 raise RunQemuError("Can't open PEM certificate %s " % pemcert)
728
729 def check_ovmf(self):
730 """Check and set full path for OVMF firmware and variable file(s)."""
731
732 for index, ovmf in enumerate(self.ovmf_bios):
733 if os.path.exists(ovmf):
734 continue
735 for suffix in ('qcow2', 'bin'):
736 path = '%s/%s.%s' % (self.get('DEPLOY_DIR_IMAGE'), ovmf, suffix)
737 if os.path.exists(path):
738 self.ovmf_bios[index] = path
739 if ovmf.endswith('secboot'):
740 self.setup_pkkek1()
741 break
742 else:
743 raise RunQemuError("Can't find OVMF firmware: %s" % ovmf)
744
745 def check_kernel(self):
746 """Check and set kernel"""
747 # The vm image doesn't need a kernel
748 if self.fstype in self.vmtypes:
749 return
750
751 # See if the user supplied a KERNEL option
752 if self.get('KERNEL'):
753 self.kernel = self.get('KERNEL')
754
755 # QB_DEFAULT_KERNEL is always a full file path
756 kernel_name = os.path.basename(self.get('QB_DEFAULT_KERNEL'))
757
758 # The user didn't want a kernel to be loaded
759 if kernel_name == "none" and not self.kernel:
760 return
761
762 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
763 if not self.kernel:
764 kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name)
765 kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
766 kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE'))
767 globs = (kernel_match_name, kernel_match_link, kernel_startswith)
768 self.kernel = get_first_file(globs)
769 if not self.kernel:
770 raise RunQemuError('KERNEL not found: %s, %s or %s' % globs)
771
772 if not os.path.exists(self.kernel):
773 raise RunQemuError("KERNEL %s not found" % self.kernel)
774
775 def check_dtb(self):
776 """Check and set dtb"""
777 # Did the user specify a device tree?
778 if self.get('DEVICE_TREE'):
779 self.dtb = self.get('DEVICE_TREE')
780 if not os.path.exists(self.dtb):
781 raise RunQemuError('Specified DTB not found: %s' % self.dtb)
782 return
783
784 dtb = self.get('QB_DTB')
785 if dtb:
786 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
787 glob_match = "%s/%s" % (deploy_dir_image, dtb)
788 glob_startswith = "%s/%s*" % (deploy_dir_image, dtb)
789 glob_wild = "%s/*.dtb" % deploy_dir_image
790 globs = (glob_match, glob_startswith, glob_wild)
791 self.dtb = get_first_file(globs)
792 if not os.path.exists(self.dtb):
793 raise RunQemuError('DTB not found: %s, %s or %s' % globs)
794
795 def check_bios(self):
796 """Check and set bios"""
797
798 # See if the user supplied a BIOS option
799 if self.get('BIOS'):
800 self.bios = self.get('BIOS')
801
802 # QB_DEFAULT_BIOS is always a full file path
803 bios_name = os.path.basename(self.get('QB_DEFAULT_BIOS'))
804
805 # The user didn't want a bios to be loaded
806 if (bios_name == "" or bios_name == "none") and not self.bios:
807 return
808
809 if not self.bios:
810 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
811 self.bios = "%s/%s" % (deploy_dir_image, bios_name)
812
813 if not self.bios:
814 raise RunQemuError('BIOS not found: %s' % bios_match_name)
815
816 if not os.path.exists(self.bios):
817 raise RunQemuError("BIOS %s not found" % self.bios)
818
819
820 def check_mem(self):
821 """
822 Both qemu and kernel needs memory settings, so check QB_MEM and set it
823 for both.
824 """
825 s = re.search('-m +([0-9]+)', self.qemuparams)
826 if s:
827 self.set('QB_MEM', '-m %s' % s.group(1))
828 elif not self.get('QB_MEM'):
829 logger.info('QB_MEM is not set, use 256M by default')
830 self.set('QB_MEM', '-m 256')
831
832 # Check and remove M or m suffix
833 qb_mem = self.get('QB_MEM')
834 if qb_mem.endswith('M') or qb_mem.endswith('m'):
835 qb_mem = qb_mem[:-1]
836
837 # Add -m prefix it not present
838 if not qb_mem.startswith('-m'):
839 qb_mem = '-m %s' % qb_mem
840
841 self.set('QB_MEM', qb_mem)
842
843 mach = self.get('MACHINE')
844 if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')):
845 self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M'
846
847 self.qemu_opt_script += ' %s' % self.get('QB_MEM')
848
849 def check_tcpserial(self):
850 if self.tcpserial_portnum:
851 ports = self.tcpserial_portnum.split(':')
852 port = ports[0]
853 if self.get('QB_TCPSERIAL_OPT'):
854 self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port)
855 else:
856 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
857
858 if len(ports) > 1:
859 for port in ports[1:]:
860 self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port
861
862 def check_and_set(self):
863 """Check configs sanity and set when needed"""
864 self.validate_paths()
865 if not self.slirp_enabled and not self.net_bridge:
866 check_tun()
867 # Check audio
868 if self.audio_enabled:
869 if not self.get('QB_AUDIO_DRV'):
870 raise RunQemuError("QB_AUDIO_DRV is NULL, this board doesn't support audio")
871 if not self.get('QB_AUDIO_OPT'):
872 logger.warning('QB_AUDIO_OPT is NULL, you may need define it to make audio work')
873 else:
874 self.qemu_opt_script += ' %s' % self.get('QB_AUDIO_OPT')
875 os.putenv('QEMU_AUDIO_DRV', self.get('QB_AUDIO_DRV'))
876 else:
877 os.putenv('QEMU_AUDIO_DRV', 'none')
878
879 self.check_qemu_system()
880 self.check_kvm()
881 self.check_fstype()
882 self.check_rootfs()
883 self.check_ovmf()
884 self.check_kernel()
885 self.check_dtb()
886 self.check_bios()
887 self.check_mem()
888 self.check_tcpserial()
889
890 def read_qemuboot(self):
891 if not self.qemuboot:
892 if self.get('DEPLOY_DIR_IMAGE'):
893 deploy_dir_image = self.get('DEPLOY_DIR_IMAGE')
894 else:
895 logger.warning("Can't find qemuboot conf file, DEPLOY_DIR_IMAGE is NULL!")
896 return
897
898 if self.rootfs and not os.path.exists(self.rootfs):
899 # Lazy rootfs
900 machine = self.get('MACHINE')
901 if not machine:
902 machine = os.path.basename(deploy_dir_image)
903 if not self.get('IMAGE_LINK_NAME'):
904 raise RunQemuError("IMAGE_LINK_NAME wasn't set to find corresponding .qemuboot.conf file")
905 self.qemuboot = "%s/%s.qemuboot.conf" % (deploy_dir_image,
906 self.get('IMAGE_LINK_NAME'))
907 else:
908 cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image
909 logger.debug('Running %s...' % cmd)
910 try:
911 qbs = subprocess.check_output(cmd, shell=True).decode('utf-8')
912 except subprocess.CalledProcessError as err:
913 raise RunQemuError(err)
914 if qbs:
915 for qb in qbs.split():
916 # Don't use initramfs when other choices unless fstype is ramfs
917 if '-initramfs-' in os.path.basename(qb) and self.fstype != 'cpio.gz':
918 continue
919 self.qemuboot = qb
920 break
921 if not self.qemuboot:
922 # Use the first one when no choice
923 self.qemuboot = qbs.split()[0]
924 self.qbconfload = True
925
926 if not self.qemuboot:
927 # If we haven't found a .qemuboot.conf at this point it probably
928 # doesn't exist, continue without
929 return
930
931 if not os.path.exists(self.qemuboot):
932 raise RunQemuError("Failed to find %s (wrong image name or BSP does not support running under qemu?)." % self.qemuboot)
933
934 logger.debug('CONFFILE: %s' % self.qemuboot)
935
936 cf = configparser.ConfigParser()
937 cf.read(self.qemuboot)
938 for k, v in cf.items('config_bsp'):
939 k_upper = k.upper()
940 if v.startswith("../"):
941 v = os.path.abspath(os.path.dirname(self.qemuboot) + "/" + v)
942 elif v == ".":
943 v = os.path.dirname(self.qemuboot)
944 self.set(k_upper, v)
945
946 def validate_paths(self):
947 """Ensure all relevant path variables are set"""
948 # When we're started with a *.qemuboot.conf arg assume that image
949 # artefacts are relative to that file, rather than in whatever
950 # directory DEPLOY_DIR_IMAGE in the conf file points to.
951 if self.qbconfload:
952 imgdir = os.path.realpath(os.path.dirname(self.qemuboot))
953 if imgdir != os.path.realpath(self.get('DEPLOY_DIR_IMAGE')):
954 logger.info('Setting DEPLOY_DIR_IMAGE to folder containing %s (%s)' % (self.qemuboot, imgdir))
955 self.set('DEPLOY_DIR_IMAGE', imgdir)
956
957 # If the STAGING_*_NATIVE directories from the config file don't exist
958 # and we're in a sourced OE build directory try to extract the paths
959 # from `bitbake -e`
960 havenative = os.path.exists(self.get('STAGING_DIR_NATIVE')) and \
961 os.path.exists(self.get('STAGING_BINDIR_NATIVE'))
962
963 if not havenative:
964 if not self.bitbake_e:
965 self.load_bitbake_env()
966
967 if self.bitbake_e:
968 native_vars = ['STAGING_DIR_NATIVE']
969 for nv in native_vars:
970 s = re.search('^%s="(.*)"' % nv, self.bitbake_e, re.M)
971 if s and s.group(1) != self.get(nv):
972 logger.info('Overriding conf file setting of %s to %s from Bitbake environment' % (nv, s.group(1)))
973 self.set(nv, s.group(1))
974 else:
975 # when we're invoked from a running bitbake instance we won't
976 # be able to call `bitbake -e`, then try:
977 # - get OE_TMPDIR from environment and guess paths based on it
978 # - get OECORE_NATIVE_SYSROOT from environment (for sdk)
979 tmpdir = self.get('OE_TMPDIR')
980 oecore_native_sysroot = self.get('OECORE_NATIVE_SYSROOT')
981 if tmpdir:
982 logger.info('Setting STAGING_DIR_NATIVE and STAGING_BINDIR_NATIVE relative to OE_TMPDIR (%s)' % tmpdir)
983 hostos, _, _, _, machine = os.uname()
984 buildsys = '%s-%s' % (machine, hostos.lower())
985 staging_dir_native = '%s/sysroots/%s' % (tmpdir, buildsys)
986 self.set('STAGING_DIR_NATIVE', staging_dir_native)
987 elif oecore_native_sysroot:
988 logger.info('Setting STAGING_DIR_NATIVE to OECORE_NATIVE_SYSROOT (%s)' % oecore_native_sysroot)
989 self.set('STAGING_DIR_NATIVE', oecore_native_sysroot)
990 if self.get('STAGING_DIR_NATIVE'):
991 # we have to assume that STAGING_BINDIR_NATIVE is at usr/bin
992 staging_bindir_native = '%s/usr/bin' % self.get('STAGING_DIR_NATIVE')
993 logger.info('Setting STAGING_BINDIR_NATIVE to %s' % staging_bindir_native)
994 self.set('STAGING_BINDIR_NATIVE', '%s/usr/bin' % self.get('STAGING_DIR_NATIVE'))
995
996 def print_config(self):
997 logoutput = ['Continuing with the following parameters:']
998 if not self.fstype in self.vmtypes:
999 logoutput.append('KERNEL: [%s]' % self.kernel)
1000 if self.bios:
1001 logoutput.append('BIOS: [%s]' % self.bios)
1002 if self.dtb:
1003 logoutput.append('DTB: [%s]' % self.dtb)
1004 logoutput.append('MACHINE: [%s]' % self.get('MACHINE'))
1005 try:
1006 fstype_flags = ' (' + ', '.join(self.fsinfo[self.fstype]) + ')'
1007 except KeyError:
1008 fstype_flags = ''
1009 logoutput.append('FSTYPE: [%s%s]' % (self.fstype, fstype_flags))
1010 if self.fstype == 'nfs':
1011 logoutput.append('NFS_DIR: [%s]' % self.rootfs)
1012 else:
1013 logoutput.append('ROOTFS: [%s]' % self.rootfs)
1014 if self.ovmf_bios:
1015 logoutput.append('OVMF: %s' % self.ovmf_bios)
1016 if (self.ovmf_secboot_pkkek1):
1017 logoutput.append('SECBOOT PKKEK1: [%s...]' % self.ovmf_secboot_pkkek1[0:100])
1018 logoutput.append('CONFFILE: [%s]' % self.qemuboot)
1019 logoutput.append('')
1020 logger.info('\n'.join(logoutput))
1021
1022 def setup_nfs(self):
1023 if not self.nfs_server:
1024 if self.slirp_enabled:
1025 self.nfs_server = '10.0.2.2'
1026 else:
1027 self.nfs_server = '192.168.7.@GATEWAY@'
1028
1029 nfsd_port = 3048 + self.nfs_instance
1030 lockdir = "/tmp/qemu-port-locks"
1031 self.make_lock_dir(lockdir)
1032 while not self.check_free_port('localhost', nfsd_port, lockdir):
1033 self.nfs_instance += 1
1034 nfsd_port += 1
1035
1036 mountd_port = nfsd_port
1037 # Export vars for runqemu-export-rootfs
1038 export_dict = {
1039 'NFS_INSTANCE': self.nfs_instance,
1040 'NFSD_PORT': nfsd_port,
1041 'MOUNTD_PORT': mountd_port,
1042 }
1043 for k, v in export_dict.items():
1044 # Use '%s' since they are integers
1045 os.putenv(k, '%s' % v)
1046
1047 qb_nfsrootfs_extra_opt = self.get("QB_NFSROOTFS_EXTRA_OPT")
1048 if qb_nfsrootfs_extra_opt and not qb_nfsrootfs_extra_opt.startswith(","):
1049 qb_nfsrootfs_extra_opt = "," + qb_nfsrootfs_extra_opt
1050
1051 self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s%s" % (nfsd_port, mountd_port, qb_nfsrootfs_extra_opt)
1052
1053 # Extract .tar.bz2 or .tar.bz if no nfs dir
1054 if not (self.rootfs and os.path.isdir(self.rootfs)):
1055 src_prefix = '%s/%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'))
1056 dest = "%s-nfsroot" % src_prefix
1057 if os.path.exists('%s.pseudo_state' % dest):
1058 logger.info('Use %s as NFS_DIR' % dest)
1059 self.rootfs = dest
1060 else:
1061 src = ""
1062 src1 = '%s.tar.bz2' % src_prefix
1063 src2 = '%s.tar.gz' % src_prefix
1064 if os.path.exists(src1):
1065 src = src1
1066 elif os.path.exists(src2):
1067 src = src2
1068 if not src:
1069 raise RunQemuError("No NFS_DIR is set, and can't find %s or %s to extract" % (src1, src2))
1070 logger.info('NFS_DIR not found, extracting %s to %s' % (src, dest))
1071 cmd = ('runqemu-extract-sdk', src, dest)
1072 logger.info('Running %s...' % str(cmd))
1073 if subprocess.call(cmd) != 0:
1074 raise RunQemuError('Failed to run %s' % str(cmd))
1075 self.rootfs = dest
1076 self.cleanup_files.append(self.rootfs)
1077 self.cleanup_files.append('%s.pseudo_state' % self.rootfs)
1078
1079 # Start the userspace NFS server
1080 cmd = ('runqemu-export-rootfs', 'start', self.rootfs)
1081 logger.info('Running %s...' % str(cmd))
1082 if subprocess.call(cmd) != 0:
1083 raise RunQemuError('Failed to run %s' % str(cmd))
1084
1085 self.nfs_running = True
1086
1087 def setup_cmd(self):
1088 cmd = self.get('QB_SETUP_CMD')
1089 if cmd != '':
1090 logger.info('Running setup command %s' % str(cmd))
1091 if subprocess.call(cmd, shell=True) != 0:
1092 raise RunQemuError('Failed to run %s' % str(cmd))
1093
1094 def setup_net_bridge(self):
1095 self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % (
1096 self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper')))
1097
1098 def make_lock_dir(self, lockdir):
1099 if not os.path.exists(lockdir):
1100 # There might be a race issue when multi runqemu processess are
1101 # running at the same time.
1102 try:
1103 os.mkdir(lockdir)
1104 os.chmod(lockdir, 0o777)
1105 except FileExistsError:
1106 pass
1107 return
1108
1109 def setup_slirp(self):
1110 """Setup user networking"""
1111
1112 if self.fstype == 'nfs':
1113 self.setup_nfs()
1114 netconf = " " + self.cmdline_ip_slirp
1115 logger.info("Network configuration:%s", netconf)
1116 self.kernel_cmdline_script += netconf
1117 # Port mapping
1118 hostfwd = ",hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23"
1119 qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE'))
1120 qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default
1121 # Figure out the port
1122 ports = re.findall('hostfwd=[^-]*:([0-9]+)-[^,-]*', qb_slirp_opt)
1123 ports = [int(i) for i in ports]
1124 mac = 2
1125
1126 lockdir = "/tmp/qemu-port-locks"
1127 self.make_lock_dir(lockdir)
1128
1129 # Find a free port to avoid conflicts
1130 for p in ports[:]:
1131 p_new = p
1132 while not self.check_free_port('localhost', p_new, lockdir):
1133 p_new += 1
1134 mac += 1
1135 while p_new in ports:
1136 p_new += 1
1137 mac += 1
1138 if p != p_new:
1139 ports.append(p_new)
1140 qb_slirp_opt = re.sub(':%s-' % p, ':%s-' % p_new, qb_slirp_opt)
1141 logger.info("Port forward changed: %s -> %s" % (p, p_new))
1142 mac = "%s%02x" % (self.mac_slirp, mac)
1143 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qb_slirp_opt))
1144 # Print out port foward
1145 hostfwd = re.findall('(hostfwd=[^,]*)', qb_slirp_opt)
1146 if hostfwd:
1147 logger.info('Port forward: %s' % ' '.join(hostfwd))
1148
1149 def setup_tap(self):
1150 """Setup tap"""
1151
1152 # This file is created when runqemu-gen-tapdevs creates a bank of tap
1153 # devices, indicating that the user should not bring up new ones using
1154 # sudo.
1155 nosudo_flag = '/etc/runqemu-nosudo'
1156 self.qemuifup = shutil.which('runqemu-ifup')
1157 self.qemuifdown = shutil.which('runqemu-ifdown')
1158 ip = shutil.which('ip')
1159 lockdir = "/tmp/qemu-tap-locks"
1160
1161 if not (self.qemuifup and self.qemuifdown and ip):
1162 logger.error("runqemu-ifup: %s" % self.qemuifup)
1163 logger.error("runqemu-ifdown: %s" % self.qemuifdown)
1164 logger.error("ip: %s" % ip)
1165 raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found")
1166
1167 self.make_lock_dir(lockdir)
1168
1169 cmd = (ip, 'link')
1170 logger.debug('Running %s...' % str(cmd))
1171 ip_link = subprocess.check_output(cmd).decode('utf-8')
1172 # Matches line like: 6: tap0: <foo>
1173 oe_tap_name = 'tap'
1174 if 'OE_TAP_NAME' in os.environ:
1175 oe_tap_name = os.environ['OE_TAP_NAME']
1176 tap_re = '^[0-9]+: +(' + oe_tap_name + '[0-9]+): <.*'
1177 possibles = re.findall(tap_re, ip_link, re.M)
1178 tap = ""
1179 for p in possibles:
1180 lockfile = os.path.join(lockdir, p)
1181 if os.path.exists('%s.skip' % lockfile):
1182 logger.info('Found %s.skip, skipping %s' % (lockfile, p))
1183 continue
1184 self.taplock = lockfile + '.lock'
1185 if self.acquire_taplock(error=False):
1186 tap = p
1187 logger.info("Using preconfigured tap device %s" % tap)
1188 logger.info("If this is not intended, touch %s.skip to make runqemu skip %s." %(lockfile, tap))
1189 break
1190
1191 if not tap:
1192 if os.path.exists(nosudo_flag):
1193 logger.error("Error: There are no available tap devices to use for networking,")
1194 logger.error("and I see %s exists, so I am not going to try creating" % nosudo_flag)
1195 raise RunQemuError("a new one with sudo.")
1196
1197 gid = os.getgid()
1198 logger.info("Setting up tap interface under sudo")
1199 cmd = ('sudo', self.qemuifup, str(gid))
1200 for _ in range(5):
1201 try:
1202 tap = subprocess.check_output(cmd).decode('utf-8').strip()
1203 except subprocess.CalledProcessError as e:
1204 logger.error('Setting up tap device failed:\n%s\nRun runqemu-gen-tapdevs to manually create one.' % str(e))
1205 sys.exit(1)
1206 lockfile = os.path.join(lockdir, tap)
1207 self.taplock = lockfile + '.lock'
1208 if self.acquire_taplock():
1209 self.cleantap = True
1210 logger.debug('Created tap: %s' % tap)
1211 break
1212 else:
1213 tap = None
1214
1215 if not tap:
1216 logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.")
1217 sys.exit(1)
1218 self.tap = tap
1219 tapnum = int(tap[len(oe_tap_name):])
1220 gateway = tapnum * 2 + 1
1221 client = gateway + 1
1222 if self.fstype == 'nfs':
1223 self.setup_nfs()
1224 netconf = " " + self.cmdline_ip_tap
1225 netconf = netconf.replace('@CLIENT@', str(client))
1226 netconf = netconf.replace('@GATEWAY@', str(gateway))
1227 self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway))
1228 logger.info("Network configuration:%s", netconf)
1229 self.kernel_cmdline_script += netconf
1230 mac = "%s%02x" % (self.mac_tap, client)
1231 qb_tap_opt = self.get('QB_TAP_OPT')
1232 if qb_tap_opt:
1233 qemu_tap_opt = qb_tap_opt.replace('@TAP@', tap)
1234 else:
1235 qemu_tap_opt = "-netdev tap,id=net0,ifname=%s,script=no,downscript=no" % (self.tap)
1236
1237 if self.vhost_enabled:
1238 qemu_tap_opt += ',vhost=on'
1239
1240 self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt))
1241
1242 def setup_network(self):
1243 if self.nonetwork or self.get('QB_NET') == 'none':
1244 self.set('NETWORK_CMD', '-nic none')
1245 return
1246 if sys.stdin.isatty():
1247 self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip()
1248 self.network_device = self.get('QB_NETWORK_DEVICE') or self.network_device
1249 if self.net_bridge:
1250 self.setup_net_bridge()
1251 elif self.slirp_enabled:
1252 self.cmdline_ip_slirp = self.get('QB_CMDLINE_IP_SLIRP') or self.cmdline_ip_slirp
1253 self.setup_slirp()
1254 else:
1255 self.cmdline_ip_tap = self.get('QB_CMDLINE_IP_TAP') or self.cmdline_ip_tap
1256 self.setup_tap()
1257
1258 def setup_rootfs(self):
1259 if self.get('QB_ROOTFS') == 'none':
1260 return
1261 if 'wic.' in self.fstype:
1262 self.fstype = self.fstype[4:]
1263 rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw'
1264
1265 tmpfsdir = os.environ.get("RUNQEMU_TMPFS_DIR", None)
1266 if self.snapshot and tmpfsdir:
1267 newrootfs = os.path.join(tmpfsdir, os.path.basename(self.rootfs)) + "." + str(os.getpid())
1268 logger.info("Copying rootfs to %s" % newrootfs)
1269 copy_start = time.time()
1270 shutil.copyfile(self.rootfs, newrootfs)
1271 logger.info("Copy done in %s seconds" % (time.time() - copy_start))
1272 self.rootfs = newrootfs
1273 # Don't need a second copy now!
1274 self.snapshot = False
1275 self.cleanup_files.append(newrootfs)
1276
1277 qb_rootfs_opt = self.get('QB_ROOTFS_OPT')
1278 if qb_rootfs_opt:
1279 self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs)
1280 else:
1281 self.rootfs_options = '-drive file=%s,if=virtio,format=%s' % (self.rootfs, rootfs_format)
1282
1283 qb_rootfs_extra_opt = self.get("QB_ROOTFS_EXTRA_OPT")
1284 if qb_rootfs_extra_opt and not qb_rootfs_extra_opt.startswith(","):
1285 qb_rootfs_extra_opt = "," + qb_rootfs_extra_opt
1286
1287 if self.fstype in ('cpio.gz', 'cpio'):
1288 self.kernel_cmdline = 'root=/dev/ram0 rw debugshell'
1289 self.rootfs_options = '-initrd %s' % self.rootfs
1290 else:
1291 vm_drive = ''
1292 if self.fstype in self.vmtypes:
1293 if self.fstype == 'iso':
1294 vm_drive = '-drive file=%s,if=virtio,media=cdrom' % self.rootfs
1295 elif self.get('QB_DRIVE_TYPE'):
1296 drive_type = self.get('QB_DRIVE_TYPE')
1297 if drive_type.startswith("/dev/sd"):
1298 logger.info('Using scsi drive')
1299 vm_drive = '-drive if=none,id=hd,file=%s,format=%s -device virtio-scsi-pci,id=scsi -device scsi-hd,drive=hd%s' \
1300 % (self.rootfs, rootfs_format, qb_rootfs_extra_opt)
1301 elif drive_type.startswith("/dev/hd"):
1302 logger.info('Using ide drive')
1303 vm_drive = "-drive file=%s,format=%s" % (self.rootfs, rootfs_format)
1304 elif drive_type.startswith("/dev/mmcblk"):
1305 logger.info('Using sdcard drive')
1306 vm_drive = '-drive id=sdcard0,if=none,file=%s,format=%s -device sdhci-pci -device sd-card,drive=sdcard0' \
1307 % (self.rootfs, rootfs_format)
1308 elif drive_type.startswith("/dev/vdb"):
1309 logger.info('Using block virtio drive');
1310 vm_drive = '-drive id=disk0,file=%s,if=none,format=%s -device virtio-blk-device,drive=disk0%s' \
1311 % (self.rootfs, rootfs_format,qb_rootfs_extra_opt)
1312 else:
1313 # virtio might have been selected explicitly (just use it), or
1314 # is used as fallback (then warn about that).
1315 if not drive_type.startswith("/dev/vd"):
1316 logger.warning("Unknown QB_DRIVE_TYPE: %s" % drive_type)
1317 logger.warning("Failed to figure out drive type, consider define or fix QB_DRIVE_TYPE")
1318 logger.warning('Trying to use virtio block drive')
1319 vm_drive = '-drive if=virtio,file=%s,format=%s' % (self.rootfs, rootfs_format)
1320
1321 # All branches above set vm_drive.
1322 self.rootfs_options = vm_drive
1323 if not self.fstype in self.vmtypes:
1324 self.rootfs_options += ' -no-reboot'
1325
1326 # By default, ' rw' is appended to QB_KERNEL_ROOT unless either ro or rw is explicitly passed.
1327 qb_kernel_root = self.get('QB_KERNEL_ROOT')
1328 qb_kernel_root_l = qb_kernel_root.split()
1329 if not ('ro' in qb_kernel_root_l or 'rw' in qb_kernel_root_l):
1330 qb_kernel_root += ' rw'
1331 self.kernel_cmdline = 'root=%s' % qb_kernel_root
1332
1333 if self.fstype == 'nfs':
1334 self.rootfs_options = ''
1335 k_root = '/dev/nfs nfsroot=%s:%s,%s' % (self.nfs_server, os.path.abspath(self.rootfs), self.unfs_opts)
1336 self.kernel_cmdline = 'root=%s rw' % k_root
1337
1338 if self.fstype == 'none':
1339 self.rootfs_options = ''
1340
1341 self.set('ROOTFS_OPTIONS', self.rootfs_options)
1342
1343 def guess_qb_system(self):
1344 """attempt to determine the appropriate qemu-system binary"""
1345 mach = self.get('MACHINE')
1346 if not mach:
1347 search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*'
1348 if self.rootfs:
1349 match = re.match(search, self.rootfs)
1350 if match:
1351 mach = match.group(1)
1352 elif self.kernel:
1353 match = re.match(search, self.kernel)
1354 if match:
1355 mach = match.group(1)
1356
1357 if not mach:
1358 return None
1359
1360 if mach == 'qemuarm':
1361 qbsys = 'arm'
1362 elif mach == 'qemuarm64':
1363 qbsys = 'aarch64'
1364 elif mach == 'qemux86':
1365 qbsys = 'i386'
1366 elif mach == 'qemux86-64':
1367 qbsys = 'x86_64'
1368 elif mach == 'qemuppc':
1369 qbsys = 'ppc'
1370 elif mach == 'qemuloongarch64':
1371 qbsys = 'loongarch64'
1372 elif mach == 'qemumips':
1373 qbsys = 'mips'
1374 elif mach == 'qemumips64':
1375 qbsys = 'mips64'
1376 elif mach == 'qemumipsel':
1377 qbsys = 'mipsel'
1378 elif mach == 'qemumips64el':
1379 qbsys = 'mips64el'
1380 elif mach == 'qemuriscv64':
1381 qbsys = 'riscv64'
1382 elif mach == 'qemuriscv32':
1383 qbsys = 'riscv32'
1384 else:
1385 logger.error("Unable to determine QEMU PC System emulator for %s machine." % mach)
1386 logger.error("As %s is not among valid QEMU machines such as," % mach)
1387 logger.error("qemux86-64, qemux86, qemuarm64, qemuarm, qemumips64, qemumips64el, qemumipsel, qemumips, qemuppc")
1388 raise RunQemuError("Set qb_system_name with suitable QEMU PC System emulator in .*qemuboot.conf.")
1389
1390 return 'qemu-system-%s' % qbsys
1391
1392 def check_qemu_system(self):
1393 qemu_system = self.get('QB_SYSTEM_NAME')
1394 if not qemu_system:
1395 qemu_system = self.guess_qb_system()
1396 if not qemu_system:
1397 raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!")
1398 self.qemu_system = qemu_system
1399
1400 def check_render_nodes(self):
1401 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."""
1402 try:
1403 content = os.listdir("/dev/dri")
1404 nodes = [i for i in content if i.startswith('renderD')]
1405 if len(nodes) == 0:
1406 raise RunQemuError("No render nodes found in /dev/dri/: %s. %s" %(content, render_hint))
1407 for n in nodes:
1408 try:
1409 with open(os.path.join("/dev/dri", n), "w") as f:
1410 f.close()
1411 break
1412 except IOError:
1413 pass
1414 else:
1415 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))
1416 except FileNotFoundError:
1417 raise RunQemuError("/dev/dri directory does not exist; no render nodes available on this machine. %s" %(render_hint))
1418
1419 def setup_guest_agent(self):
1420 if self.guest_agent == True:
1421 self.qemu_opt += ' -chardev socket,path=' + self.guest_agent_sockpath + ',server,nowait,id=qga0 '
1422 self.qemu_opt += ' -device virtio-serial '
1423 self.qemu_opt += ' -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 '
1424
1425 def setup_qmp(self):
1426 if self.qmp:
1427 self.qemu_opt += " -qmp %s,server,nowait" % self.qmp
1428
1429 def setup_vga(self):
1430 if self.nographic == True:
1431 if self.sdl == True:
1432 raise RunQemuError('Option nographic makes no sense alongside the sdl option.')
1433 if self.gtk == True:
1434 raise RunQemuError('Option nographic makes no sense alongside the gtk option.')
1435 self.qemu_opt += ' -nographic'
1436
1437 if self.novga == True:
1438 self.qemu_opt += ' -vga none'
1439 return
1440
1441 if (self.gl_es == True or self.gl == True) and (self.sdl == False and self.gtk == False):
1442 raise RunQemuError('Option gl/gl-es needs gtk or sdl option.')
1443
1444 # If we have no display option, we autodetect based upon what qemu supports. We
1445 # need our font setup and show-cusor below so we need to see what qemu --help says
1446 # is supported so we can pass our correct config in.
1447 if not self.nographic and not self.sdl and not self.gtk and not self.publicvnc and not self.egl_headless == True:
1448 output = subprocess.check_output([self.qemu_bin, "--help"], universal_newlines=True, env=self.qemu_environ)
1449 if "-display gtk" in output:
1450 self.gtk = True
1451 elif "-display sdl" in output:
1452 self.sdl = True
1453 else:
1454 self.qemu_opt += ' -display none'
1455
1456 if self.sdl == True or self.gtk == True or self.egl_headless == True:
1457
1458 if self.qemu_system.endswith(('i386', 'x86_64')):
1459 if self.gl or self.gl_es or self.egl_headless:
1460 self.qemu_opt += ' -device virtio-vga-gl '
1461 else:
1462 self.qemu_opt += ' -device virtio-vga '
1463
1464 self.qemu_opt += ' -display '
1465 if self.egl_headless == True:
1466 self.check_render_nodes()
1467 self.set_mesa_paths()
1468 self.qemu_opt += 'egl-headless,'
1469 else:
1470 if self.sdl == True:
1471 self.qemu_opt += 'sdl,'
1472 elif self.gtk == True:
1473 self.qemu_environ['FONTCONFIG_PATH'] = '/etc/fonts'
1474 self.qemu_opt += 'gtk,'
1475
1476 if self.gl == True:
1477 self.set_mesa_paths()
1478 self.qemu_opt += 'gl=on,'
1479 elif self.gl_es == True:
1480 self.set_mesa_paths()
1481 self.qemu_opt += 'gl=es,'
1482 self.qemu_opt += 'show-cursor=on'
1483
1484 self.qemu_opt += ' %s' %self.get('QB_GRAPHICS')
1485
1486 def setup_serial(self):
1487 # Setup correct kernel command line for serial
1488 if self.get('SERIAL_CONSOLES') and (self.serialstdio == True or self.serialconsole == True or self.nographic == True or self.tcpserial_portnum):
1489 for entry in self.get('SERIAL_CONSOLES').split(' '):
1490 self.kernel_cmdline_script += ' console=%s' %entry.split(';')[1]
1491
1492 # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES).
1493 # If no serial or serialtcp options were specified, only ttyS0 is created
1494 # and sysvinit shows an error trying to enable ttyS1:
1495 # INIT: Id "S1" respawning too fast: disabled for 5 minutes
1496 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1497
1498 # Assume if the user passed serial options, they know what they want
1499 # and pad to two devices
1500 if serial_num == 1:
1501 self.qemu_opt += " -serial null"
1502 elif serial_num >= 2:
1503 return
1504
1505 if self.serialstdio == True or self.nographic == True:
1506 self.qemu_opt += " -serial mon:stdio"
1507 else:
1508 self.qemu_opt += " -serial mon:vc"
1509 if self.serialconsole:
1510 if sys.stdin.isatty():
1511 subprocess.check_call(("stty", "intr", "^]"))
1512 logger.info("Interrupt character is '^]'")
1513
1514 self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT")
1515
1516 serial_num = len(re.findall("(^| )-serial ", self.qemu_opt))
1517 if serial_num < 2:
1518 self.qemu_opt += " -serial null"
1519
1520 def find_qemu(self):
1521 qemu_bin = os.path.join(self.bindir_native, self.qemu_system)
1522
1523 # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't
1524 # find QEMU in sysroot, it needs to use host's qemu.
1525 if not os.path.exists(qemu_bin):
1526 logger.info("QEMU binary not found in %s, trying host's QEMU" % qemu_bin)
1527 for path in (os.environ['PATH'] or '').split(':'):
1528 qemu_bin_tmp = os.path.join(path, self.qemu_system)
1529 logger.info("Trying: %s" % qemu_bin_tmp)
1530 if os.path.exists(qemu_bin_tmp):
1531 qemu_bin = qemu_bin_tmp
1532 if not os.path.isabs(qemu_bin):
1533 qemu_bin = os.path.abspath(qemu_bin)
1534 logger.info("Using host's QEMU: %s" % qemu_bin)
1535 break
1536
1537 if not os.access(qemu_bin, os.X_OK):
1538 raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin)
1539 self.qemu_bin = qemu_bin
1540
1541 def setup_final(self):
1542
1543 self.find_qemu()
1544
1545 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')))
1546
1547 for ovmf in self.ovmf_bios:
1548 format = ovmf.rsplit('.', 1)[-1]
1549 if format == "bin":
1550 format = "raw"
1551 self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf)
1552
1553 self.qemu_opt += ' ' + self.qemu_opt_script
1554
1555 if self.ovmf_secboot_pkkek1:
1556 # Provide the Platform Key and first Key Exchange Key certificate as an
1557 # OEM string in the SMBIOS Type 11 table. Prepend the certificate string
1558 # with "application prefix" of the EnrollDefaultKeys.efi application
1559 self.qemu_opt += ' -smbios type=11,value=4e32566d-8e9e-4f52-81d3-5bb9715f9727:' \
1560 + self.ovmf_secboot_pkkek1
1561
1562 # Append qemuparams to override previous settings
1563 if self.qemuparams:
1564 self.qemu_opt += ' ' + self.qemuparams
1565
1566 if self.snapshot:
1567 self.qemu_opt += " -snapshot"
1568
1569 self.setup_guest_agent()
1570 self.setup_qmp()
1571 self.setup_serial()
1572 self.setup_vga()
1573
1574 def start_qemu(self):
1575 import shlex
1576 if self.kernel:
1577 kernel_opts = "-kernel %s" % (self.kernel)
1578 if self.get('QB_KERNEL_CMDLINE') == "none":
1579 if self.bootparams:
1580 kernel_opts += " -append '%s'" % (self.bootparams)
1581 else:
1582 kernel_opts += " -append '%s %s %s %s'" % (self.kernel_cmdline,
1583 self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'),
1584 self.bootparams)
1585 if self.dtb:
1586 kernel_opts += " -dtb %s" % self.dtb
1587 else:
1588 kernel_opts = ""
1589
1590 if self.bios:
1591 self.qemu_opt += " -bios %s" % self.bios
1592
1593 cmd = "%s %s" % (self.qemu_opt, kernel_opts)
1594 cmds = shlex.split(cmd)
1595 logger.info('Running %s\n' % cmd)
1596 with open('/proc/uptime', 'r') as f:
1597 uptime_seconds = f.readline().split()[0]
1598 logger.info('Host uptime: %s\n' % uptime_seconds)
1599 pass_fds = []
1600 if self.taplock_descriptor:
1601 pass_fds = [self.taplock_descriptor.fileno()]
1602 if len(self.portlocks):
1603 for descriptor in self.portlocks.values():
1604 pass_fds.append(descriptor.fileno())
1605 process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ)
1606 self.qemuprocess = process
1607 retcode = process.wait()
1608 if retcode:
1609 if retcode == -signal.SIGTERM:
1610 logger.info("Qemu terminated by SIGTERM")
1611 else:
1612 logger.error("Failed to run qemu: %s", process.stderr.read().decode())
1613
1614 def cleanup_cmd(self):
1615 cmd = self.get('QB_CLEANUP_CMD')
1616 if cmd != '':
1617 logger.info('Running cleanup command %s' % str(cmd))
1618 if subprocess.call(cmd, shell=True) != 0:
1619 raise RunQemuError('Failed to run %s' % str(cmd))
1620
1621 def cleanup(self):
1622 if self.cleaned:
1623 return
1624
1625 # avoid dealing with SIGTERM when cleanup function is running
1626 signal.signal(signal.SIGTERM, signal.SIG_IGN)
1627
1628 logger.info("Cleaning up")
1629
1630 if self.qemuprocess:
1631 try:
1632 # give it some time to shut down, ignore return values and output
1633 self.qemuprocess.send_signal(signal.SIGTERM)
1634 self.qemuprocess.communicate(timeout=5)
1635 except subprocess.TimeoutExpired:
1636 self.qemuprocess.kill()
1637
1638 with open('/proc/uptime', 'r') as f:
1639 uptime_seconds = f.readline().split()[0]
1640 logger.info('Host uptime: %s\n' % uptime_seconds)
1641 if self.cleantap:
1642 cmd = ('sudo', self.qemuifdown, self.tap)
1643 logger.debug('Running %s' % str(cmd))
1644 subprocess.check_call(cmd)
1645 self.release_taplock()
1646
1647 if self.nfs_running:
1648 logger.info("Shutting down the userspace NFS server...")
1649 cmd = ("runqemu-export-rootfs", "stop", self.rootfs)
1650 logger.debug('Running %s' % str(cmd))
1651 subprocess.check_call(cmd)
1652 self.release_portlock()
1653
1654 if self.saved_stty:
1655 subprocess.check_call(("stty", self.saved_stty))
1656
1657 if self.cleanup_files:
1658 for ent in self.cleanup_files:
1659 logger.info('Removing %s' % ent)
1660 if os.path.isfile(ent):
1661 os.remove(ent)
1662 else:
1663 shutil.rmtree(ent)
1664
1665 # Deliberately ignore the return code of 'tput smam'.
1666 subprocess.call(["tput", "smam"])
1667
1668 self.cleaned = True
1669
1670 def run_bitbake_env(self, mach=None, target=''):
1671 bitbake = shutil.which('bitbake')
1672 if not bitbake:
1673 return
1674
1675 if not mach:
1676 mach = self.get('MACHINE')
1677
1678 multiconfig = self.get('MULTICONFIG')
1679 if multiconfig:
1680 multiconfig = "mc:%s" % multiconfig
1681
1682 if self.rootfs and not target:
1683 target = self.rootfs
1684
1685 if mach:
1686 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1687 else:
1688 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1689
1690 logger.info('Running %s...' % cmd)
1691 try:
1692 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1693 except subprocess.CalledProcessError as err:
1694 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')))
1695 # need something with IMAGE_NAME_SUFFIX/IMAGE_LINK_NAME defined (kernel also inherits image-artifact-names.bbclass)
1696 target = 'virtual/kernel'
1697 if mach:
1698 cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target)
1699 else:
1700 cmd = 'bitbake -e %s %s' % (multiconfig, target)
1701 try:
1702 return subprocess.check_output(cmd, shell=True).decode('utf-8')
1703 except subprocess.CalledProcessError as err:
1704 logger.warning("Couldn't run '%s' to gather environment information, giving up with 'bitbake -e':\n%s" % (cmd, err.output.decode('utf-8')))
1705 return ''
1706
1707
1708 def load_bitbake_env(self, mach=None, target=None):
1709 if self.bitbake_e:
1710 return
1711
1712 self.bitbake_e = self.run_bitbake_env(mach=mach, target=target)
1713
1714 def validate_combos(self):
1715 if (self.fstype in self.vmtypes) and self.kernel:
1716 raise RunQemuError("%s doesn't need kernel %s!" % (self.fstype, self.kernel))
1717
1718 @property
1719 def bindir_native(self):
1720 result = self.get('STAGING_BINDIR_NATIVE')
1721 if result and os.path.exists(result):
1722 return result
1723
1724 cmd = ['bitbake', '-e']
1725 multiconfig = self.get('MULTICONFIG')
1726 if multiconfig:
1727 cmd.append('mc:%s:qemu-helper-native' % multiconfig)
1728 else:
1729 cmd.append('qemu-helper-native')
1730
1731 logger.info('Running %s...' % str(cmd))
1732 out = subprocess.check_output(cmd).decode('utf-8')
1733
1734 match = re.search('^STAGING_BINDIR_NATIVE="(.*)"', out, re.M)
1735 if match:
1736 result = match.group(1)
1737 if os.path.exists(result):
1738 self.set('STAGING_BINDIR_NATIVE', result)
1739 return result
1740 raise RunQemuError("Native sysroot directory %s doesn't exist" % result)
1741 else:
1742 raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd))
1743
1744
1745def main():
1746 if "help" in sys.argv or '-h' in sys.argv or '--help' in sys.argv:
1747 print_usage()
1748 return 0
1749 try:
1750 config = BaseConfig()
1751
1752 renice = os.path.expanduser("~/bin/runqemu-renice")
1753 if os.path.exists(renice):
1754 logger.info('Using %s to renice' % renice)
1755 subprocess.check_call([renice, str(os.getpid())])
1756
1757 def sigterm_handler(signum, frame):
1758 logger.info("Received signal: %s" % (signum))
1759 config.cleanup()
1760 signal.signal(signal.SIGTERM, sigterm_handler)
1761
1762 config.check_args()
1763 config.read_qemuboot()
1764 config.check_and_set()
1765 # Check whether the combos is valid or not
1766 config.validate_combos()
1767 config.print_config()
1768 config.setup_network()
1769 config.setup_rootfs()
1770 config.setup_final()
1771 config.setup_cmd()
1772 config.start_qemu()
1773 except RunQemuError as err:
1774 logger.error(err)
1775 return 1
1776 except Exception as err:
1777 import traceback
1778 traceback.print_exc()
1779 return 1
1780 finally:
1781 config.cleanup_cmd()
1782 config.cleanup()
1783
1784if __name__ == "__main__":
1785 sys.exit(main())