diff options
Diffstat (limited to 'scripts/runqemu')
-rwxr-xr-x | scripts/runqemu | 625 |
1 files changed, 417 insertions, 208 deletions
diff --git a/scripts/runqemu b/scripts/runqemu index 532f2e338d..69cd44864e 100755 --- a/scripts/runqemu +++ b/scripts/runqemu | |||
@@ -18,6 +18,7 @@ import shutil | |||
18 | import glob | 18 | import glob |
19 | import configparser | 19 | import configparser |
20 | import signal | 20 | import signal |
21 | import time | ||
21 | 22 | ||
22 | class RunQemuError(Exception): | 23 | class RunQemuError(Exception): |
23 | """Custom exception to raise on known errors.""" | 24 | """Custom exception to raise on known errors.""" |
@@ -65,20 +66,25 @@ of the following environment variables (in any order): | |||
65 | MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) | 66 | MACHINE - the machine name (optional, autodetected from KERNEL filename if unspecified) |
66 | Simplified QEMU command-line options can be passed with: | 67 | Simplified QEMU command-line options can be passed with: |
67 | nographic - disable video console | 68 | nographic - disable video console |
69 | nonetwork - disable network connectivity | ||
68 | novga - Disable VGA emulation completely | 70 | novga - Disable VGA emulation completely |
69 | sdl - choose the SDL UI frontend | 71 | sdl - choose the SDL UI frontend |
70 | gtk - choose the Gtk UI frontend | 72 | gtk - choose the Gtk UI frontend |
71 | gl - enable virgl-based GL acceleration (also needs gtk or sdl options) | 73 | gl - enable virgl-based GL acceleration (also needs gtk or sdl options) |
72 | gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) | 74 | gl-es - enable virgl-based GL acceleration, using OpenGL ES (also needs gtk or sdl options) |
73 | egl-headless - enable headless EGL output; use vnc (via publicvnc option) or spice to see it | 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) | ||
74 | serial - enable a serial console on /dev/ttyS0 | 78 | serial - enable a serial console on /dev/ttyS0 |
75 | serialstdio - enable a serial console on the console (regardless of graphics mode) | 79 | serialstdio - enable a serial console on the console (regardless of graphics mode) |
76 | slirp - enable user networking, no root privileges is required | 80 | slirp - enable user networking, no root privilege is required |
77 | snapshot - don't write changes to back to images | 81 | snapshot - don't write changes back to images |
78 | kvm - enable KVM when running x86/x86_64 (VT-capable CPU required) | 82 | kvm - enable KVM when running x86/x86_64 (VT-capable CPU required) |
79 | kvm-vhost - enable KVM with vhost 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) |
80 | publicvnc - enable a VNC server open to all hosts | 84 | publicvnc - enable a VNC server open to all hosts |
81 | audio - enable audio | 85 | audio - enable audio |
86 | guestagent - enable guest agent communication | ||
87 | qmp=<path> - create a QMP socket (defaults to unix:qmp.sock if unspecified) | ||
82 | [*/]ovmf* - OVMF firmware file or base name for booting with UEFI | 88 | [*/]ovmf* - OVMF firmware file or base name for booting with UEFI |
83 | tcpserial=<port> - specify tcp serial port number | 89 | tcpserial=<port> - specify tcp serial port number |
84 | qemuparams=<xyz> - specify custom parameters to QEMU | 90 | qemuparams=<xyz> - specify custom parameters to QEMU |
@@ -113,10 +119,10 @@ def check_tun(): | |||
113 | if not os.access(dev_tun, os.W_OK): | 119 | if not os.access(dev_tun, os.W_OK): |
114 | raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) | 120 | raise RunQemuError("TUN control device %s is not writable, please fix (e.g. sudo chmod 666 %s)" % (dev_tun, dev_tun)) |
115 | 121 | ||
116 | def get_first_file(cmds): | 122 | def get_first_file(globs): |
117 | """Return first file found in wildcard cmds""" | 123 | """Return first file found in wildcard globs""" |
118 | for cmd in cmds: | 124 | for g in globs: |
119 | all_files = glob.glob(cmd) | 125 | all_files = glob.glob(g) |
120 | if all_files: | 126 | if all_files: |
121 | for f in all_files: | 127 | for f in all_files: |
122 | if not os.path.isdir(f): | 128 | if not os.path.isdir(f): |
@@ -139,12 +145,12 @@ class BaseConfig(object): | |||
139 | 'OE_TMPDIR', | 145 | 'OE_TMPDIR', |
140 | 'OECORE_NATIVE_SYSROOT', | 146 | 'OECORE_NATIVE_SYSROOT', |
141 | 'MULTICONFIG', | 147 | 'MULTICONFIG', |
148 | 'SERIAL_CONSOLES', | ||
142 | ) | 149 | ) |
143 | 150 | ||
144 | self.qemu_opt = '' | 151 | self.qemu_opt = '' |
145 | self.qemu_opt_script = '' | 152 | self.qemu_opt_script = '' |
146 | self.qemuparams = '' | 153 | self.qemuparams = '' |
147 | self.clean_nfs_dir = False | ||
148 | self.nfs_server = '' | 154 | self.nfs_server = '' |
149 | self.rootfs = '' | 155 | self.rootfs = '' |
150 | # File name(s) of a OVMF firmware file or variable store, | 156 | # File name(s) of a OVMF firmware file or variable store, |
@@ -173,6 +179,15 @@ class BaseConfig(object): | |||
173 | self.nfs_running = False | 179 | self.nfs_running = False |
174 | self.serialconsole = False | 180 | self.serialconsole = False |
175 | self.serialstdio = 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 | ||
176 | self.cleantap = False | 191 | self.cleantap = False |
177 | self.saved_stty = '' | 192 | self.saved_stty = '' |
178 | self.audio_enabled = False | 193 | self.audio_enabled = False |
@@ -184,12 +199,14 @@ class BaseConfig(object): | |||
184 | self.snapshot = False | 199 | self.snapshot = False |
185 | self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") | 200 | self.wictypes = ('wic', 'wic.vmdk', 'wic.qcow2', 'wic.vdi', "wic.vhd", "wic.vhdx") |
186 | self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', | 201 | self.fstypes = ('ext2', 'ext3', 'ext4', 'jffs2', 'nfs', 'btrfs', |
187 | 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz') | 202 | 'cpio.gz', 'cpio', 'ramfs', 'tar.bz2', 'tar.gz', |
203 | 'squashfs', 'squashfs-xz', 'squashfs-lzo', | ||
204 | 'squashfs-lz4', 'squashfs-zst') | ||
188 | self.vmtypes = ('hddimg', 'iso') | 205 | self.vmtypes = ('hddimg', 'iso') |
189 | self.fsinfo = {} | 206 | self.fsinfo = {} |
190 | self.network_device = "-device e1000,netdev=net0,mac=@MAC@" | 207 | self.network_device = "-device e1000,netdev=net0,mac=@MAC@" |
191 | self.cmdline_ip_slirp = "ip=dhcp" | 208 | self.cmdline_ip_slirp = "ip=dhcp" |
192 | self.cmdline_ip_tap = "ip=192.168.7.@CLIENT@::192.168.7.@GATEWAY@:255.255.255.0" | 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" |
193 | # Use different mac section for tap and slirp to avoid | 210 | # Use different mac section for tap and slirp to avoid |
194 | # conflicts, e.g., when one is running with tap, the other is | 211 | # conflicts, e.g., when one is running with tap, the other is |
195 | # running with slirp. | 212 | # running with slirp. |
@@ -199,9 +216,15 @@ class BaseConfig(object): | |||
199 | self.mac_tap = "52:54:00:12:34:" | 216 | self.mac_tap = "52:54:00:12:34:" |
200 | self.mac_slirp = "52:54:00:12:35:" | 217 | self.mac_slirp = "52:54:00:12:35:" |
201 | # pid of the actual qemu process | 218 | # pid of the actual qemu process |
202 | self.qemupid = None | 219 | self.qemu_environ = os.environ.copy() |
220 | self.qemuprocess = None | ||
203 | # avoid cleanup twice | 221 | # avoid cleanup twice |
204 | self.cleaned = False | 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' | ||
205 | 228 | ||
206 | def acquire_taplock(self, error=True): | 229 | def acquire_taplock(self, error=True): |
207 | logger.debug("Acquiring lockfile %s..." % self.taplock) | 230 | logger.debug("Acquiring lockfile %s..." % self.taplock) |
@@ -223,9 +246,12 @@ class BaseConfig(object): | |||
223 | def release_taplock(self): | 246 | def release_taplock(self): |
224 | if self.taplock_descriptor: | 247 | if self.taplock_descriptor: |
225 | logger.debug("Releasing lockfile for tap device '%s'" % self.tap) | 248 | logger.debug("Releasing lockfile for tap device '%s'" % self.tap) |
226 | fcntl.flock(self.taplock_descriptor, fcntl.LOCK_UN) | 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) | ||
227 | self.taplock_descriptor.close() | 252 | self.taplock_descriptor.close() |
228 | os.remove(self.taplock) | 253 | # Removing the file is a potential race, don't do that either |
254 | # os.remove(self.taplock) | ||
229 | self.taplock_descriptor = None | 255 | self.taplock_descriptor = None |
230 | 256 | ||
231 | def check_free_port(self, host, port, lockdir): | 257 | def check_free_port(self, host, port, lockdir): |
@@ -263,17 +289,23 @@ class BaseConfig(object): | |||
263 | 289 | ||
264 | def release_portlock(self, lockfile=None): | 290 | def release_portlock(self, lockfile=None): |
265 | if lockfile != None: | 291 | if lockfile != None: |
266 | logger.debug("Releasing lockfile '%s'" % lockfile) | 292 | logger.debug("Releasing lockfile '%s'" % lockfile) |
267 | fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) | 293 | # We pass the fd to the qemu process and if we unlock here, it would unlock for |
268 | self.portlocks[lockfile].close() | 294 | # that too. Therefore don't unlock, just close |
269 | os.remove(lockfile) | 295 | # fcntl.flock(self.portlocks[lockfile], fcntl.LOCK_UN) |
270 | del self.portlocks[lockfile] | 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] | ||
271 | elif len(self.portlocks): | 300 | elif len(self.portlocks): |
272 | for lockfile, descriptor in self.portlocks.items(): | 301 | for lockfile, descriptor in self.portlocks.items(): |
273 | logger.debug("Releasing lockfile '%s'" % lockfile) | 302 | logger.debug("Releasing lockfile '%s'" % lockfile) |
274 | fcntl.flock(descriptor, fcntl.LOCK_UN) | 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) | ||
275 | descriptor.close() | 306 | descriptor.close() |
276 | os.remove(lockfile) | 307 | # Removing the file is a potential race, don't do that either |
308 | # os.remove(lockfile) | ||
277 | self.portlocks = {} | 309 | self.portlocks = {} |
278 | 310 | ||
279 | def get(self, key): | 311 | def get(self, key): |
@@ -331,21 +363,21 @@ class BaseConfig(object): | |||
331 | def check_arg_path(self, p): | 363 | def check_arg_path(self, p): |
332 | """ | 364 | """ |
333 | - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf | 365 | - Check whether it is <image>.qemuboot.conf or contains <image>.qemuboot.conf |
334 | - Check whether is a kernel file | 366 | - Check whether it is a kernel file |
335 | - Check whether is a image file | 367 | - Check whether it is an image file |
336 | - Check whether it is a nfs dir | 368 | - Check whether it is an NFS dir |
337 | - Check whether it is a OVMF flash file | 369 | - Check whether it is an OVMF flash file |
338 | """ | 370 | """ |
339 | if p.endswith('.qemuboot.conf'): | 371 | if p.endswith('.qemuboot.conf'): |
340 | self.qemuboot = p | 372 | self.qemuboot = p |
341 | self.qbconfload = True | 373 | self.qbconfload = True |
342 | elif re.search('\.bin$', p) or re.search('bzImage', p) or \ | 374 | elif re.search('\\.bin$', p) or re.search('bzImage', p) or \ |
343 | re.search('zImage', p) or re.search('vmlinux', p) or \ | 375 | re.search('zImage', p) or re.search('vmlinux', p) or \ |
344 | re.search('fitImage', p) or re.search('uImage', p): | 376 | re.search('fitImage', p) or re.search('uImage', p): |
345 | self.kernel = p | 377 | self.kernel = p |
346 | elif os.path.exists(p) and (not os.path.isdir(p)) and '-image-' in os.path.basename(p): | 378 | elif os.path.isfile(p) and ('-image-' in os.path.basename(p) or '.rootfs.' in os.path.basename(p)): |
347 | self.rootfs = p | 379 | self.rootfs = p |
348 | # Check filename against self.fstypes can hanlde <file>.cpio.gz, | 380 | # Check filename against self.fstypes can handle <file>.cpio.gz, |
349 | # otherwise, its type would be "gz", which is incorrect. | 381 | # otherwise, its type would be "gz", which is incorrect. |
350 | fst = "" | 382 | fst = "" |
351 | for t in self.fstypes: | 383 | for t in self.fstypes: |
@@ -353,18 +385,24 @@ class BaseConfig(object): | |||
353 | fst = t | 385 | fst = t |
354 | break | 386 | break |
355 | if not fst: | 387 | if not fst: |
356 | m = re.search('.*\.(.*)$', self.rootfs) | 388 | m = re.search('.*\\.(.*)$', self.rootfs) |
357 | if m: | 389 | if m: |
358 | fst = m.group(1) | 390 | fst = m.group(1) |
359 | if fst: | 391 | if fst: |
360 | self.check_arg_fstype(fst) | 392 | self.check_arg_fstype(fst) |
361 | qb = re.sub('\.' + fst + "$", '', self.rootfs) | 393 | qb = re.sub('\\.' + fst + "$", '.qemuboot.conf', self.rootfs) |
362 | qb = '%s%s' % (re.sub('\.rootfs$', '', qb), '.qemuboot.conf') | ||
363 | if os.path.exists(qb): | 394 | if os.path.exists(qb): |
364 | self.qemuboot = qb | 395 | self.qemuboot = qb |
365 | self.qbconfload = True | 396 | self.qbconfload = True |
366 | else: | 397 | else: |
367 | logger.warning("%s doesn't exist" % qb) | 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) | ||
368 | else: | 406 | else: |
369 | raise RunQemuError("Can't find FSTYPE from: %s" % p) | 407 | raise RunQemuError("Can't find FSTYPE from: %s" % p) |
370 | 408 | ||
@@ -398,6 +436,7 @@ class BaseConfig(object): | |||
398 | # are there other scenarios in which we need to support being | 436 | # are there other scenarios in which we need to support being |
399 | # invoked by bitbake? | 437 | # invoked by bitbake? |
400 | deploy = self.get('DEPLOY_DIR_IMAGE') | 438 | deploy = self.get('DEPLOY_DIR_IMAGE') |
439 | image_link_name = self.get('IMAGE_LINK_NAME') | ||
401 | bbchild = deploy and self.get('OE_TMPDIR') | 440 | bbchild = deploy and self.get('OE_TMPDIR') |
402 | if bbchild: | 441 | if bbchild: |
403 | self.set_machine_deploy_dir(arg, deploy) | 442 | self.set_machine_deploy_dir(arg, deploy) |
@@ -422,23 +461,24 @@ class BaseConfig(object): | |||
422 | else: | 461 | else: |
423 | logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) | 462 | logger.error("%s not a directory valid DEPLOY_DIR_IMAGE" % deploy_dir_image) |
424 | self.set("MACHINE", arg) | 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) | ||
425 | 470 | ||
426 | def set_dri_path(self): | 471 | def set_dri_path(self): |
427 | # As runqemu can be run within bitbake (when using testimage, for example), | 472 | drivers_path = os.path.join(self.bindir_native, '../lib/dri') |
428 | # we need to ensure that we run host pkg-config, and that it does not | 473 | if not os.path.exists(drivers_path) or not os.listdir(drivers_path): |
429 | # get mis-directed to native build paths set by bitbake. | 474 | raise RunQemuError(""" |
430 | try: | 475 | qemu has been built without opengl support and accelerated graphics support is not available. |
431 | del os.environ['PKG_CONFIG_PATH'] | 476 | To enable it, add: |
432 | del os.environ['PKG_CONFIG_DIR'] | 477 | DISTRO_FEATURES_NATIVE:append = " opengl" |
433 | del os.environ['PKG_CONFIG_LIBDIR'] | 478 | DISTRO_FEATURES_NATIVESDK:append = " opengl" |
434 | del os.environ['PKG_CONFIG_SYSROOT_DIR'] | 479 | to your build configuration. |
435 | except KeyError: | 480 | """) |
436 | pass | 481 | self.qemu_environ['LIBGL_DRIVERS_PATH'] = drivers_path |
437 | try: | ||
438 | dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True) | ||
439 | except subprocess.CalledProcessError as e: | ||
440 | raise RunQemuError("Could not determine the path to dri drivers on the host via pkg-config.\nPlease install Mesa development files (particularly, dri.pc) on the host machine.") | ||
441 | os.environ['LIBGL_DRIVERS_PATH'] = dripath.decode('utf-8').strip() | ||
442 | 482 | ||
443 | def check_args(self): | 483 | def check_args(self): |
444 | for debug in ("-d", "--debug"): | 484 | for debug in ("-d", "--debug"): |
@@ -452,51 +492,32 @@ class BaseConfig(object): | |||
452 | sys.argv.remove(quiet) | 492 | sys.argv.remove(quiet) |
453 | 493 | ||
454 | if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: | 494 | if 'gl' not in sys.argv[1:] and 'gl-es' not in sys.argv[1:]: |
455 | os.environ['SDL_RENDER_DRIVER'] = 'software' | 495 | self.qemu_environ['SDL_RENDER_DRIVER'] = 'software' |
496 | self.qemu_environ['SDL_FRAMEBUFFER_ACCELERATION'] = 'false' | ||
456 | 497 | ||
457 | unknown_arg = "" | 498 | unknown_arg = "" |
458 | for arg in sys.argv[1:]: | 499 | for arg in sys.argv[1:]: |
459 | if arg in self.fstypes + self.vmtypes + self.wictypes: | 500 | if arg in self.fstypes + self.vmtypes + self.wictypes: |
460 | self.check_arg_fstype(arg) | 501 | self.check_arg_fstype(arg) |
461 | elif arg == 'nographic': | 502 | elif arg == 'nographic': |
462 | if ('sdl' in sys.argv): | 503 | self.nographic = True |
463 | raise RunQemuError('Option nographic makes no sense alongside the sdl option.' % (arg)) | 504 | elif arg == "nonetwork": |
464 | if ('gtk' in sys.argv): | 505 | self.nonetwork = True |
465 | raise RunQemuError('Option nographic makes no sense alongside the gtk option.' % (arg)) | ||
466 | self.qemu_opt_script += ' -nographic' | ||
467 | self.kernel_cmdline_script += ' console=ttyS0' | ||
468 | elif arg == 'sdl': | 506 | elif arg == 'sdl': |
469 | if 'gl' in sys.argv[1:]: | 507 | self.sdl = True |
470 | self.set_dri_path() | ||
471 | self.qemu_opt_script += ' -vga virtio -display sdl,gl=on,show-cursor=on' | ||
472 | elif 'gl-es' in sys.argv[1:]: | ||
473 | self.set_dri_path() | ||
474 | self.qemu_opt_script += ' -vga virtio -display sdl,gl=es,show-cursor=on' | ||
475 | else: | ||
476 | self.qemu_opt_script += ' -display sdl,show-cursor=on' | ||
477 | elif arg == 'gtk': | 508 | elif arg == 'gtk': |
478 | if 'gl' in sys.argv[1:]: | 509 | self.gtk = True |
479 | self.set_dri_path() | 510 | elif arg == 'gl': |
480 | self.qemu_opt_script += ' -vga virtio -display gtk,gl=on,show-cursor=on' | 511 | self.gl = True |
481 | elif 'gl-es' in sys.argv[1:]: | 512 | elif arg == 'gl-es': |
482 | self.set_dri_path() | 513 | self.gl_es = True |
483 | self.qemu_opt_script += ' -vga virtio -display gtk,gl=es,show-cursor=on' | ||
484 | else: | ||
485 | self.qemu_opt_script += ' -display gtk,show-cursor=on' | ||
486 | elif arg == 'gl' or arg == 'gl-es': | ||
487 | # These args are handled inside sdl or gtk blocks above | ||
488 | if ('gtk' not in sys.argv) and ('sdl' not in sys.argv): | ||
489 | raise RunQemuError('Option %s also needs gtk or sdl option.' % (arg)) | ||
490 | elif arg == 'egl-headless': | 514 | elif arg == 'egl-headless': |
491 | self.set_dri_path() | 515 | self.egl_headless = True |
492 | self.qemu_opt_script += ' -vga virtio -display egl-headless,show-cursor=on' | ||
493 | elif arg == 'novga': | 516 | elif arg == 'novga': |
494 | self.qemu_opt_script += ' -vga none' | 517 | self.novga = True |
495 | elif arg == 'serial': | 518 | elif arg == 'serial': |
496 | self.kernel_cmdline_script += ' console=ttyS0' | ||
497 | self.serialconsole = True | 519 | self.serialconsole = True |
498 | elif arg == "serialstdio": | 520 | elif arg == "serialstdio": |
499 | self.kernel_cmdline_script += ' console=ttyS0' | ||
500 | self.serialstdio = True | 521 | self.serialstdio = True |
501 | elif arg == 'audio': | 522 | elif arg == 'audio': |
502 | logger.info("Enabling audio in qemu") | 523 | logger.info("Enabling audio in qemu") |
@@ -513,7 +534,16 @@ class BaseConfig(object): | |||
513 | elif arg == 'snapshot': | 534 | elif arg == 'snapshot': |
514 | self.snapshot = True | 535 | self.snapshot = True |
515 | elif arg == 'publicvnc': | 536 | elif arg == 'publicvnc': |
537 | self.publicvnc = True | ||
516 | self.qemu_opt_script += ' -vnc :0' | 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='):] | ||
517 | elif arg.startswith('tcpserial='): | 547 | elif arg.startswith('tcpserial='): |
518 | self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] | 548 | self.tcpserial_portnum = '%s' % arg[len('tcpserial='):] |
519 | elif arg.startswith('qemuparams='): | 549 | elif arg.startswith('qemuparams='): |
@@ -545,21 +575,28 @@ class BaseConfig(object): | |||
545 | self.check_arg_machine(unknown_arg) | 575 | self.check_arg_machine(unknown_arg) |
546 | 576 | ||
547 | if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): | 577 | if not (self.get('DEPLOY_DIR_IMAGE') or self.qbconfload): |
548 | self.load_bitbake_env() | 578 | self.load_bitbake_env(target=self.rootfs) |
549 | s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) | 579 | s = re.search('^DEPLOY_DIR_IMAGE="(.*)"', self.bitbake_e, re.M) |
550 | if s: | 580 | if s: |
551 | self.set("DEPLOY_DIR_IMAGE", s.group(1)) | 581 | self.set("DEPLOY_DIR_IMAGE", s.group(1)) |
552 | 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 | |||
553 | def check_kvm(self): | 590 | def check_kvm(self): |
554 | """Check kvm and kvm-host""" | 591 | """Check kvm and kvm-host""" |
555 | if not (self.kvm_enabled or self.vhost_enabled): | 592 | if not (self.kvm_enabled or self.vhost_enabled): |
556 | self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU')) | 593 | self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU'), self.get('QB_SMP')) |
557 | return | 594 | return |
558 | 595 | ||
559 | if not self.get('QB_CPU_KVM'): | 596 | if not self.get('QB_CPU_KVM'): |
560 | raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") | 597 | raise RunQemuError("QB_CPU_KVM is NULL, this board doesn't support kvm") |
561 | 598 | ||
562 | self.qemu_opt_script += ' %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM')) | 599 | self.qemu_opt_script += ' %s %s %s' % (self.get('QB_MACHINE'), self.get('QB_CPU_KVM'), self.get('QB_SMP')) |
563 | yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" | 600 | yocto_kvm_wiki = "https://wiki.yoctoproject.org/wiki/How_to_enable_KVM_for_Poky_qemu" |
564 | yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" | 601 | yocto_paravirt_kvm_wiki = "https://wiki.yoctoproject.org/wiki/Running_an_x86_Yocto_Linux_image_under_QEMU_KVM" |
565 | dev_kvm = '/dev/kvm' | 602 | dev_kvm = '/dev/kvm' |
@@ -579,11 +616,6 @@ class BaseConfig(object): | |||
579 | 616 | ||
580 | if os.access(dev_kvm, os.W_OK|os.R_OK): | 617 | if os.access(dev_kvm, os.W_OK|os.R_OK): |
581 | self.qemu_opt_script += ' -enable-kvm' | 618 | self.qemu_opt_script += ' -enable-kvm' |
582 | if self.get('MACHINE') == "qemux86": | ||
583 | # Workaround for broken APIC window on pre 4.15 host kernels which causes boot hangs | ||
584 | # See YOCTO #12301 | ||
585 | # On 64 bit we use x2apic | ||
586 | self.kernel_cmdline_script += " clocksource=kvm-clock hpet=disable noapic nolapic" | ||
587 | else: | 619 | else: |
588 | logger.error("You have no read or write permission on /dev/kvm.") | 620 | logger.error("You have no read or write permission on /dev/kvm.") |
589 | logger.error("Please change the ownership of this file as described at:") | 621 | logger.error("Please change the ownership of this file as described at:") |
@@ -624,10 +656,10 @@ class BaseConfig(object): | |||
624 | elif fsflag == 'kernel-in-fs': | 656 | elif fsflag == 'kernel-in-fs': |
625 | wic_fs = False | 657 | wic_fs = False |
626 | else: | 658 | else: |
627 | logger.warn('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) | 659 | logger.warning('Unknown flag "%s:%s" in QB_FSINFO', fstype, fsflag) |
628 | continue | 660 | continue |
629 | else: | 661 | else: |
630 | logger.warn('QB_FSINFO is not supported for image type "%s"', fstype) | 662 | logger.warning('QB_FSINFO is not supported for image type "%s"', fstype) |
631 | continue | 663 | continue |
632 | 664 | ||
633 | if fstype in self.fsinfo: | 665 | if fstype in self.fsinfo: |
@@ -660,16 +692,16 @@ class BaseConfig(object): | |||
660 | 692 | ||
661 | if self.rootfs and not os.path.exists(self.rootfs): | 693 | if self.rootfs and not os.path.exists(self.rootfs): |
662 | # Lazy rootfs | 694 | # Lazy rootfs |
663 | self.rootfs = "%s/%s-%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), | 695 | self.rootfs = "%s/%s.%s" % (self.get('DEPLOY_DIR_IMAGE'), |
664 | self.rootfs, self.get('MACHINE'), | 696 | self.get('IMAGE_LINK_NAME'), |
665 | self.fstype) | 697 | self.fstype) |
666 | elif not self.rootfs: | 698 | elif not self.rootfs: |
667 | cmd_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) | 699 | glob_name = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_NAME'), self.fstype) |
668 | cmd_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) | 700 | glob_link = '%s/%s*.%s' % (self.get('DEPLOY_DIR_IMAGE'), self.get('IMAGE_LINK_NAME'), self.fstype) |
669 | cmds = (cmd_name, cmd_link) | 701 | globs = (glob_name, glob_link) |
670 | self.rootfs = get_first_file(cmds) | 702 | self.rootfs = get_first_file(globs) |
671 | if not self.rootfs: | 703 | if not self.rootfs: |
672 | raise RunQemuError("Failed to find rootfs: %s or %s" % cmds) | 704 | raise RunQemuError("Failed to find rootfs: %s or %s" % globs) |
673 | 705 | ||
674 | if not os.path.exists(self.rootfs): | 706 | if not os.path.exists(self.rootfs): |
675 | raise RunQemuError("Can't find rootfs: %s" % self.rootfs) | 707 | raise RunQemuError("Can't find rootfs: %s" % self.rootfs) |
@@ -729,10 +761,10 @@ class BaseConfig(object): | |||
729 | kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) | 761 | kernel_match_name = "%s/%s" % (deploy_dir_image, kernel_name) |
730 | kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) | 762 | kernel_match_link = "%s/%s" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) |
731 | kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) | 763 | kernel_startswith = "%s/%s*" % (deploy_dir_image, self.get('KERNEL_IMAGETYPE')) |
732 | cmds = (kernel_match_name, kernel_match_link, kernel_startswith) | 764 | globs = (kernel_match_name, kernel_match_link, kernel_startswith) |
733 | self.kernel = get_first_file(cmds) | 765 | self.kernel = get_first_file(globs) |
734 | if not self.kernel: | 766 | if not self.kernel: |
735 | raise RunQemuError('KERNEL not found: %s, %s or %s' % cmds) | 767 | raise RunQemuError('KERNEL not found: %s, %s or %s' % globs) |
736 | 768 | ||
737 | if not os.path.exists(self.kernel): | 769 | if not os.path.exists(self.kernel): |
738 | raise RunQemuError("KERNEL %s not found" % self.kernel) | 770 | raise RunQemuError("KERNEL %s not found" % self.kernel) |
@@ -749,13 +781,13 @@ class BaseConfig(object): | |||
749 | dtb = self.get('QB_DTB') | 781 | dtb = self.get('QB_DTB') |
750 | if dtb: | 782 | if dtb: |
751 | deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') | 783 | deploy_dir_image = self.get('DEPLOY_DIR_IMAGE') |
752 | cmd_match = "%s/%s" % (deploy_dir_image, dtb) | 784 | glob_match = "%s/%s" % (deploy_dir_image, dtb) |
753 | cmd_startswith = "%s/%s*" % (deploy_dir_image, dtb) | 785 | glob_startswith = "%s/%s*" % (deploy_dir_image, dtb) |
754 | cmd_wild = "%s/*.dtb" % deploy_dir_image | 786 | glob_wild = "%s/*.dtb" % deploy_dir_image |
755 | cmds = (cmd_match, cmd_startswith, cmd_wild) | 787 | globs = (glob_match, glob_startswith, glob_wild) |
756 | self.dtb = get_first_file(cmds) | 788 | self.dtb = get_first_file(globs) |
757 | if not os.path.exists(self.dtb): | 789 | if not os.path.exists(self.dtb): |
758 | raise RunQemuError('DTB not found: %s, %s or %s' % cmds) | 790 | raise RunQemuError('DTB not found: %s, %s or %s' % globs) |
759 | 791 | ||
760 | def check_bios(self): | 792 | def check_bios(self): |
761 | """Check and set bios""" | 793 | """Check and set bios""" |
@@ -779,7 +811,7 @@ class BaseConfig(object): | |||
779 | raise RunQemuError('BIOS not found: %s' % bios_match_name) | 811 | raise RunQemuError('BIOS not found: %s' % bios_match_name) |
780 | 812 | ||
781 | if not os.path.exists(self.bios): | 813 | if not os.path.exists(self.bios): |
782 | raise RunQemuError("KERNEL %s not found" % self.bios) | 814 | raise RunQemuError("BIOS %s not found" % self.bios) |
783 | 815 | ||
784 | 816 | ||
785 | def check_mem(self): | 817 | def check_mem(self): |
@@ -806,7 +838,7 @@ class BaseConfig(object): | |||
806 | self.set('QB_MEM', qb_mem) | 838 | self.set('QB_MEM', qb_mem) |
807 | 839 | ||
808 | mach = self.get('MACHINE') | 840 | mach = self.get('MACHINE') |
809 | if not mach.startswith('qemumips'): | 841 | if not mach.startswith(('qemumips', 'qemux86', 'qemuloongarch64')): |
810 | self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' | 842 | self.kernel_cmdline_script += ' mem=%s' % self.get('QB_MEM').replace('-m','').strip() + 'M' |
811 | 843 | ||
812 | self.qemu_opt_script += ' %s' % self.get('QB_MEM') | 844 | self.qemu_opt_script += ' %s' % self.get('QB_MEM') |
@@ -818,11 +850,11 @@ class BaseConfig(object): | |||
818 | if self.get('QB_TCPSERIAL_OPT'): | 850 | if self.get('QB_TCPSERIAL_OPT'): |
819 | self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) | 851 | self.qemu_opt_script += ' ' + self.get('QB_TCPSERIAL_OPT').replace('@PORT@', port) |
820 | else: | 852 | else: |
821 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port | 853 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port |
822 | 854 | ||
823 | if len(ports) > 1: | 855 | if len(ports) > 1: |
824 | for port in ports[1:]: | 856 | for port in ports[1:]: |
825 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s' % port | 857 | self.qemu_opt_script += ' -serial tcp:127.0.0.1:%s,nodelay=on' % port |
826 | 858 | ||
827 | def check_and_set(self): | 859 | def check_and_set(self): |
828 | """Check configs sanity and set when needed""" | 860 | """Check configs sanity and set when needed""" |
@@ -865,8 +897,10 @@ class BaseConfig(object): | |||
865 | machine = self.get('MACHINE') | 897 | machine = self.get('MACHINE') |
866 | if not machine: | 898 | if not machine: |
867 | machine = os.path.basename(deploy_dir_image) | 899 | machine = os.path.basename(deploy_dir_image) |
868 | self.qemuboot = "%s/%s-%s.qemuboot.conf" % (deploy_dir_image, | 900 | if not self.get('IMAGE_LINK_NAME'): |
869 | self.rootfs, machine) | 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')) | ||
870 | else: | 904 | else: |
871 | cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image | 905 | cmd = 'ls -t %s/*.qemuboot.conf' % deploy_dir_image |
872 | logger.debug('Running %s...' % cmd) | 906 | logger.debug('Running %s...' % cmd) |
@@ -987,19 +1021,16 @@ class BaseConfig(object): | |||
987 | if self.slirp_enabled: | 1021 | if self.slirp_enabled: |
988 | self.nfs_server = '10.0.2.2' | 1022 | self.nfs_server = '10.0.2.2' |
989 | else: | 1023 | else: |
990 | self.nfs_server = '192.168.7.1' | 1024 | self.nfs_server = '192.168.7.@GATEWAY@' |
991 | 1025 | ||
992 | # Figure out a new nfs_instance to allow multiple qemus running. | 1026 | nfsd_port = 3048 + self.nfs_instance |
993 | ps = subprocess.check_output(("ps", "auxww")).decode('utf-8') | 1027 | lockdir = "/tmp/qemu-port-locks" |
994 | pattern = '/bin/unfsd .* -i .*\.pid -e .*/exports([0-9]+) ' | 1028 | self.make_lock_dir(lockdir) |
995 | all_instances = re.findall(pattern, ps, re.M) | 1029 | while not self.check_free_port('localhost', nfsd_port, lockdir): |
996 | if all_instances: | 1030 | self.nfs_instance += 1 |
997 | all_instances.sort(key=int) | 1031 | nfsd_port += 1 |
998 | self.nfs_instance = int(all_instances.pop()) + 1 | ||
999 | |||
1000 | nfsd_port = 3049 + 2 * self.nfs_instance | ||
1001 | mountd_port = 3048 + 2 * self.nfs_instance | ||
1002 | 1032 | ||
1033 | mountd_port = nfsd_port | ||
1003 | # Export vars for runqemu-export-rootfs | 1034 | # Export vars for runqemu-export-rootfs |
1004 | export_dict = { | 1035 | export_dict = { |
1005 | 'NFS_INSTANCE': self.nfs_instance, | 1036 | 'NFS_INSTANCE': self.nfs_instance, |
@@ -1010,7 +1041,11 @@ class BaseConfig(object): | |||
1010 | # Use '%s' since they are integers | 1041 | # Use '%s' since they are integers |
1011 | os.putenv(k, '%s' % v) | 1042 | os.putenv(k, '%s' % v) |
1012 | 1043 | ||
1013 | self.unfs_opts="nfsvers=3,port=%s,tcp,mountport=%s" % (nfsd_port, mountd_port) | 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) | ||
1014 | 1049 | ||
1015 | # Extract .tar.bz2 or .tar.bz if no nfs dir | 1050 | # Extract .tar.bz2 or .tar.bz if no nfs dir |
1016 | if not (self.rootfs and os.path.isdir(self.rootfs)): | 1051 | if not (self.rootfs and os.path.isdir(self.rootfs)): |
@@ -1033,22 +1068,41 @@ class BaseConfig(object): | |||
1033 | cmd = ('runqemu-extract-sdk', src, dest) | 1068 | cmd = ('runqemu-extract-sdk', src, dest) |
1034 | logger.info('Running %s...' % str(cmd)) | 1069 | logger.info('Running %s...' % str(cmd)) |
1035 | if subprocess.call(cmd) != 0: | 1070 | if subprocess.call(cmd) != 0: |
1036 | raise RunQemuError('Failed to run %s' % cmd) | 1071 | raise RunQemuError('Failed to run %s' % str(cmd)) |
1037 | self.clean_nfs_dir = True | ||
1038 | self.rootfs = dest | 1072 | self.rootfs = dest |
1073 | self.cleanup_files.append(self.rootfs) | ||
1074 | self.cleanup_files.append('%s.pseudo_state' % self.rootfs) | ||
1039 | 1075 | ||
1040 | # Start the userspace NFS server | 1076 | # Start the userspace NFS server |
1041 | cmd = ('runqemu-export-rootfs', 'start', self.rootfs) | 1077 | cmd = ('runqemu-export-rootfs', 'start', self.rootfs) |
1042 | logger.info('Running %s...' % str(cmd)) | 1078 | logger.info('Running %s...' % str(cmd)) |
1043 | if subprocess.call(cmd) != 0: | 1079 | if subprocess.call(cmd) != 0: |
1044 | raise RunQemuError('Failed to run %s' % cmd) | 1080 | raise RunQemuError('Failed to run %s' % str(cmd)) |
1045 | 1081 | ||
1046 | self.nfs_running = True | 1082 | self.nfs_running = True |
1047 | 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 | |||
1048 | def setup_net_bridge(self): | 1091 | def setup_net_bridge(self): |
1049 | self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( | 1092 | self.set('NETWORK_CMD', '-netdev bridge,br=%s,id=net0,helper=%s -device virtio-net-pci,netdev=net0 ' % ( |
1050 | self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) | 1093 | self.net_bridge, os.path.join(self.bindir_native, 'qemu-oe-bridge-helper'))) |
1051 | 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 | |||
1052 | def setup_slirp(self): | 1106 | def setup_slirp(self): |
1053 | """Setup user networking""" | 1107 | """Setup user networking""" |
1054 | 1108 | ||
@@ -1058,7 +1112,7 @@ class BaseConfig(object): | |||
1058 | logger.info("Network configuration:%s", netconf) | 1112 | logger.info("Network configuration:%s", netconf) |
1059 | self.kernel_cmdline_script += netconf | 1113 | self.kernel_cmdline_script += netconf |
1060 | # Port mapping | 1114 | # Port mapping |
1061 | hostfwd = ",hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23" | 1115 | hostfwd = ",hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:2323-:23" |
1062 | qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) | 1116 | qb_slirp_opt_default = "-netdev user,id=net0%s,tftp=%s" % (hostfwd, self.get('DEPLOY_DIR_IMAGE')) |
1063 | qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default | 1117 | qb_slirp_opt = self.get('QB_SLIRP_OPT') or qb_slirp_opt_default |
1064 | # Figure out the port | 1118 | # Figure out the port |
@@ -1067,14 +1121,7 @@ class BaseConfig(object): | |||
1067 | mac = 2 | 1121 | mac = 2 |
1068 | 1122 | ||
1069 | lockdir = "/tmp/qemu-port-locks" | 1123 | lockdir = "/tmp/qemu-port-locks" |
1070 | if not os.path.exists(lockdir): | 1124 | self.make_lock_dir(lockdir) |
1071 | # There might be a race issue when multi runqemu processess are | ||
1072 | # running at the same time. | ||
1073 | try: | ||
1074 | os.mkdir(lockdir) | ||
1075 | os.chmod(lockdir, 0o777) | ||
1076 | except FileExistsError: | ||
1077 | pass | ||
1078 | 1125 | ||
1079 | # Find a free port to avoid conflicts | 1126 | # Find a free port to avoid conflicts |
1080 | for p in ports[:]: | 1127 | for p in ports[:]: |
@@ -1114,20 +1161,17 @@ class BaseConfig(object): | |||
1114 | logger.error("ip: %s" % ip) | 1161 | logger.error("ip: %s" % ip) |
1115 | raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") | 1162 | raise OEPathError("runqemu-ifup, runqemu-ifdown or ip not found") |
1116 | 1163 | ||
1117 | if not os.path.exists(lockdir): | 1164 | self.make_lock_dir(lockdir) |
1118 | # There might be a race issue when multi runqemu processess are | ||
1119 | # running at the same time. | ||
1120 | try: | ||
1121 | os.mkdir(lockdir) | ||
1122 | os.chmod(lockdir, 0o777) | ||
1123 | except FileExistsError: | ||
1124 | pass | ||
1125 | 1165 | ||
1126 | cmd = (ip, 'link') | 1166 | cmd = (ip, 'link') |
1127 | logger.debug('Running %s...' % str(cmd)) | 1167 | logger.debug('Running %s...' % str(cmd)) |
1128 | ip_link = subprocess.check_output(cmd).decode('utf-8') | 1168 | ip_link = subprocess.check_output(cmd).decode('utf-8') |
1129 | # Matches line like: 6: tap0: <foo> | 1169 | # Matches line like: 6: tap0: <foo> |
1130 | possibles = re.findall('^[0-9]+: +(tap[0-9]+): <.*', ip_link, re.M) | 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) | ||
1131 | tap = "" | 1175 | tap = "" |
1132 | for p in possibles: | 1176 | for p in possibles: |
1133 | lockfile = os.path.join(lockdir, p) | 1177 | lockfile = os.path.join(lockdir, p) |
@@ -1150,7 +1194,7 @@ class BaseConfig(object): | |||
1150 | gid = os.getgid() | 1194 | gid = os.getgid() |
1151 | uid = os.getuid() | 1195 | uid = os.getuid() |
1152 | logger.info("Setting up tap interface under sudo") | 1196 | logger.info("Setting up tap interface under sudo") |
1153 | cmd = ('sudo', self.qemuifup, str(uid), str(gid), self.bindir_native) | 1197 | cmd = ('sudo', self.qemuifup, str(gid)) |
1154 | try: | 1198 | try: |
1155 | tap = subprocess.check_output(cmd).decode('utf-8').strip() | 1199 | tap = subprocess.check_output(cmd).decode('utf-8').strip() |
1156 | except subprocess.CalledProcessError as e: | 1200 | except subprocess.CalledProcessError as e: |
@@ -1166,7 +1210,7 @@ class BaseConfig(object): | |||
1166 | logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") | 1210 | logger.error("Failed to setup tap device. Run runqemu-gen-tapdevs to manually create.") |
1167 | sys.exit(1) | 1211 | sys.exit(1) |
1168 | self.tap = tap | 1212 | self.tap = tap |
1169 | tapnum = int(tap[3:]) | 1213 | tapnum = int(tap[len(oe_tap_name):]) |
1170 | gateway = tapnum * 2 + 1 | 1214 | gateway = tapnum * 2 + 1 |
1171 | client = gateway + 1 | 1215 | client = gateway + 1 |
1172 | if self.fstype == 'nfs': | 1216 | if self.fstype == 'nfs': |
@@ -1174,6 +1218,7 @@ class BaseConfig(object): | |||
1174 | netconf = " " + self.cmdline_ip_tap | 1218 | netconf = " " + self.cmdline_ip_tap |
1175 | netconf = netconf.replace('@CLIENT@', str(client)) | 1219 | netconf = netconf.replace('@CLIENT@', str(client)) |
1176 | netconf = netconf.replace('@GATEWAY@', str(gateway)) | 1220 | netconf = netconf.replace('@GATEWAY@', str(gateway)) |
1221 | self.nfs_server = self.nfs_server.replace('@GATEWAY@', str(gateway)) | ||
1177 | logger.info("Network configuration:%s", netconf) | 1222 | logger.info("Network configuration:%s", netconf) |
1178 | self.kernel_cmdline_script += netconf | 1223 | self.kernel_cmdline_script += netconf |
1179 | mac = "%s%02x" % (self.mac_tap, client) | 1224 | mac = "%s%02x" % (self.mac_tap, client) |
@@ -1189,7 +1234,8 @@ class BaseConfig(object): | |||
1189 | self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) | 1234 | self.set('NETWORK_CMD', '%s %s' % (self.network_device.replace('@MAC@', mac), qemu_tap_opt)) |
1190 | 1235 | ||
1191 | def setup_network(self): | 1236 | def setup_network(self): |
1192 | if self.get('QB_NET') == 'none': | 1237 | if self.nonetwork or self.get('QB_NET') == 'none': |
1238 | self.set('NETWORK_CMD', '-nic none') | ||
1193 | return | 1239 | return |
1194 | if sys.stdin.isatty(): | 1240 | if sys.stdin.isatty(): |
1195 | self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() | 1241 | self.saved_stty = subprocess.check_output(("stty", "-g")).decode('utf-8').strip() |
@@ -1210,6 +1256,18 @@ class BaseConfig(object): | |||
1210 | self.fstype = self.fstype[4:] | 1256 | self.fstype = self.fstype[4:] |
1211 | rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' | 1257 | rootfs_format = self.fstype if self.fstype in ('vmdk', 'vhd', 'vhdx', 'qcow2', 'vdi') else 'raw' |
1212 | 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 | |||
1213 | qb_rootfs_opt = self.get('QB_ROOTFS_OPT') | 1271 | qb_rootfs_opt = self.get('QB_ROOTFS_OPT') |
1214 | if qb_rootfs_opt: | 1272 | if qb_rootfs_opt: |
1215 | self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) | 1273 | self.rootfs_options = qb_rootfs_opt.replace('@ROOTFS@', self.rootfs) |
@@ -1254,7 +1312,13 @@ class BaseConfig(object): | |||
1254 | self.rootfs_options = vm_drive | 1312 | self.rootfs_options = vm_drive |
1255 | if not self.fstype in self.vmtypes: | 1313 | if not self.fstype in self.vmtypes: |
1256 | self.rootfs_options += ' -no-reboot' | 1314 | self.rootfs_options += ' -no-reboot' |
1257 | self.kernel_cmdline = 'root=%s rw' % (self.get('QB_KERNEL_ROOT')) | 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 | ||
1258 | 1322 | ||
1259 | if self.fstype == 'nfs': | 1323 | if self.fstype == 'nfs': |
1260 | self.rootfs_options = '' | 1324 | self.rootfs_options = '' |
@@ -1270,7 +1334,7 @@ class BaseConfig(object): | |||
1270 | """attempt to determine the appropriate qemu-system binary""" | 1334 | """attempt to determine the appropriate qemu-system binary""" |
1271 | mach = self.get('MACHINE') | 1335 | mach = self.get('MACHINE') |
1272 | if not mach: | 1336 | if not mach: |
1273 | search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' | 1337 | search = '.*(qemux86-64|qemux86|qemuarm64|qemuarm|qemuloongarch64|qemumips64|qemumips64el|qemumipsel|qemumips|qemuppc).*' |
1274 | if self.rootfs: | 1338 | if self.rootfs: |
1275 | match = re.match(search, self.rootfs) | 1339 | match = re.match(search, self.rootfs) |
1276 | if match: | 1340 | if match: |
@@ -1293,6 +1357,8 @@ class BaseConfig(object): | |||
1293 | qbsys = 'x86_64' | 1357 | qbsys = 'x86_64' |
1294 | elif mach == 'qemuppc': | 1358 | elif mach == 'qemuppc': |
1295 | qbsys = 'ppc' | 1359 | qbsys = 'ppc' |
1360 | elif mach == 'qemuloongarch64': | ||
1361 | qbsys = 'loongarch64' | ||
1296 | elif mach == 'qemumips': | 1362 | elif mach == 'qemumips': |
1297 | qbsys = 'mips' | 1363 | qbsys = 'mips' |
1298 | elif mach == 'qemumips64': | 1364 | elif mach == 'qemumips64': |
@@ -1321,7 +1387,127 @@ class BaseConfig(object): | |||
1321 | raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") | 1387 | raise RunQemuError("Failed to boot, QB_SYSTEM_NAME is NULL!") |
1322 | self.qemu_system = qemu_system | 1388 | self.qemu_system = qemu_system |
1323 | 1389 | ||
1324 | def setup_final(self): | 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): | ||
1325 | qemu_bin = os.path.join(self.bindir_native, self.qemu_system) | 1511 | qemu_bin = os.path.join(self.bindir_native, self.qemu_system) |
1326 | 1512 | ||
1327 | # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't | 1513 | # It is possible to have qemu-native in ASSUME_PROVIDED, and it won't |
@@ -1340,11 +1526,18 @@ class BaseConfig(object): | |||
1340 | 1526 | ||
1341 | if not os.access(qemu_bin, os.X_OK): | 1527 | if not os.access(qemu_bin, os.X_OK): |
1342 | raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) | 1528 | raise OEPathError("No QEMU binary '%s' could be found" % qemu_bin) |
1529 | self.qemu_bin = qemu_bin | ||
1343 | 1530 | ||
1344 | self.qemu_opt = "%s %s %s %s %s" % (qemu_bin, self.get('NETWORK_CMD'), self.get('QB_RNG'), self.get('ROOTFS_OPTIONS'), self.get('QB_OPT_APPEND')) | 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'))) | ||
1345 | 1536 | ||
1346 | for ovmf in self.ovmf_bios: | 1537 | for ovmf in self.ovmf_bios: |
1347 | format = ovmf.rsplit('.', 1)[-1] | 1538 | format = ovmf.rsplit('.', 1)[-1] |
1539 | if format == "bin": | ||
1540 | format = "raw" | ||
1348 | self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) | 1541 | self.qemu_opt += ' -drive if=pflash,format=%s,file=%s' % (format, ovmf) |
1349 | 1542 | ||
1350 | self.qemu_opt += ' ' + self.qemu_opt_script | 1543 | self.qemu_opt += ' ' + self.qemu_opt_script |
@@ -1363,61 +1556,44 @@ class BaseConfig(object): | |||
1363 | if self.snapshot: | 1556 | if self.snapshot: |
1364 | self.qemu_opt += " -snapshot" | 1557 | self.qemu_opt += " -snapshot" |
1365 | 1558 | ||
1366 | if self.serialconsole: | 1559 | self.setup_guest_agent() |
1367 | if sys.stdin.isatty(): | 1560 | self.setup_qmp() |
1368 | subprocess.check_call(("stty", "intr", "^]")) | 1561 | self.setup_serial() |
1369 | logger.info("Interrupt character is '^]'") | 1562 | self.setup_vga() |
1370 | |||
1371 | first_serial = "" | ||
1372 | if not re.search("-nographic", self.qemu_opt): | ||
1373 | first_serial = "-serial mon:vc" | ||
1374 | # We always want a ttyS1. Since qemu by default adds a serial | ||
1375 | # port when nodefaults is not specified, it seems that all that | ||
1376 | # would be needed is to make sure a "-serial" is there. However, | ||
1377 | # it appears that when "-serial" is specified, it ignores the | ||
1378 | # default serial port that is normally added. So here we make | ||
1379 | # sure to add two -serial if there are none. And only one if | ||
1380 | # there is one -serial already. | ||
1381 | serial_num = len(re.findall("-serial", self.qemu_opt)) | ||
1382 | if serial_num == 0: | ||
1383 | self.qemu_opt += " %s %s" % (first_serial, self.get("QB_SERIAL_OPT")) | ||
1384 | elif serial_num == 1: | ||
1385 | self.qemu_opt += " %s" % self.get("QB_SERIAL_OPT") | ||
1386 | |||
1387 | # We always wants ttyS0 and ttyS1 in qemu machines (see SERIAL_CONSOLES), | ||
1388 | # if not serial or serialtcp options was specified only ttyS0 is created | ||
1389 | # and sysvinit shows an error trying to enable ttyS1: | ||
1390 | # INIT: Id "S1" respawning too fast: disabled for 5 minutes | ||
1391 | serial_num = len(re.findall("-serial", self.qemu_opt)) | ||
1392 | if serial_num == 0: | ||
1393 | if re.search("-nographic", self.qemu_opt) or self.serialstdio: | ||
1394 | self.qemu_opt += " -serial mon:stdio -serial null" | ||
1395 | else: | ||
1396 | self.qemu_opt += " -serial mon:vc -serial null" | ||
1397 | 1563 | ||
1398 | def start_qemu(self): | 1564 | def start_qemu(self): |
1399 | import shlex | 1565 | import shlex |
1400 | if self.kernel: | 1566 | if self.kernel: |
1401 | kernel_opts = "-kernel %s -append '%s %s %s %s'" % (self.kernel, self.kernel_cmdline, | 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, | ||
1402 | self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), | 1573 | self.kernel_cmdline_script, self.get('QB_KERNEL_CMDLINE_APPEND'), |
1403 | self.bootparams) | 1574 | self.bootparams) |
1404 | if self.bios: | ||
1405 | kernel_opts += " -bios %s" % self.bios | ||
1406 | if self.dtb: | 1575 | if self.dtb: |
1407 | kernel_opts += " -dtb %s" % self.dtb | 1576 | kernel_opts += " -dtb %s" % self.dtb |
1408 | else: | 1577 | else: |
1409 | kernel_opts = "" | 1578 | kernel_opts = "" |
1579 | |||
1580 | if self.bios: | ||
1581 | self.qemu_opt += " -bios %s" % self.bios | ||
1582 | |||
1410 | cmd = "%s %s" % (self.qemu_opt, kernel_opts) | 1583 | cmd = "%s %s" % (self.qemu_opt, kernel_opts) |
1411 | cmds = shlex.split(cmd) | 1584 | cmds = shlex.split(cmd) |
1412 | logger.info('Running %s\n' % 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) | ||
1413 | pass_fds = [] | 1589 | pass_fds = [] |
1414 | if self.taplock_descriptor: | 1590 | if self.taplock_descriptor: |
1415 | pass_fds = [self.taplock_descriptor.fileno()] | 1591 | pass_fds = [self.taplock_descriptor.fileno()] |
1416 | if len(self.portlocks): | 1592 | if len(self.portlocks): |
1417 | for descriptor in self.portlocks.values(): | 1593 | for descriptor in self.portlocks.values(): |
1418 | pass_fds.append(descriptor.fileno()) | 1594 | pass_fds.append(descriptor.fileno()) |
1419 | process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds) | 1595 | process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ) |
1420 | self.qemupid = process.pid | 1596 | self.qemuprocess = process |
1421 | retcode = process.wait() | 1597 | retcode = process.wait() |
1422 | if retcode: | 1598 | if retcode: |
1423 | if retcode == -signal.SIGTERM: | 1599 | if retcode == -signal.SIGTERM: |
@@ -1425,6 +1601,13 @@ class BaseConfig(object): | |||
1425 | else: | 1601 | else: |
1426 | logger.error("Failed to run qemu: %s", process.stderr.read().decode()) | 1602 | logger.error("Failed to run qemu: %s", process.stderr.read().decode()) |
1427 | 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 | |||
1428 | def cleanup(self): | 1611 | def cleanup(self): |
1429 | if self.cleaned: | 1612 | if self.cleaned: |
1430 | return | 1613 | return |
@@ -1433,30 +1616,48 @@ class BaseConfig(object): | |||
1433 | signal.signal(signal.SIGTERM, signal.SIG_IGN) | 1616 | signal.signal(signal.SIGTERM, signal.SIG_IGN) |
1434 | 1617 | ||
1435 | logger.info("Cleaning up") | 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) | ||
1436 | if self.cleantap: | 1631 | if self.cleantap: |
1437 | cmd = ('sudo', self.qemuifdown, self.tap, self.bindir_native) | 1632 | cmd = ('sudo', self.qemuifdown, self.tap) |
1438 | logger.debug('Running %s' % str(cmd)) | 1633 | logger.debug('Running %s' % str(cmd)) |
1439 | subprocess.check_call(cmd) | 1634 | subprocess.check_call(cmd) |
1440 | self.release_taplock() | 1635 | self.release_taplock() |
1441 | self.release_portlock() | ||
1442 | 1636 | ||
1443 | if self.nfs_running: | 1637 | if self.nfs_running: |
1444 | logger.info("Shutting down the userspace NFS server...") | 1638 | logger.info("Shutting down the userspace NFS server...") |
1445 | cmd = ("runqemu-export-rootfs", "stop", self.rootfs) | 1639 | cmd = ("runqemu-export-rootfs", "stop", self.rootfs) |
1446 | logger.debug('Running %s' % str(cmd)) | 1640 | logger.debug('Running %s' % str(cmd)) |
1447 | subprocess.check_call(cmd) | 1641 | subprocess.check_call(cmd) |
1642 | self.release_portlock() | ||
1448 | 1643 | ||
1449 | if self.saved_stty: | 1644 | if self.saved_stty: |
1450 | subprocess.check_call(("stty", self.saved_stty)) | 1645 | subprocess.check_call(("stty", self.saved_stty)) |
1451 | 1646 | ||
1452 | if self.clean_nfs_dir: | 1647 | if self.cleanup_files: |
1453 | logger.info('Removing %s' % self.rootfs) | 1648 | for ent in self.cleanup_files: |
1454 | shutil.rmtree(self.rootfs) | 1649 | logger.info('Removing %s' % ent) |
1455 | shutil.rmtree('%s.pseudo_state' % self.rootfs) | 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"]) | ||
1456 | 1657 | ||
1457 | self.cleaned = True | 1658 | self.cleaned = True |
1458 | 1659 | ||
1459 | def run_bitbake_env(self, mach=None): | 1660 | def run_bitbake_env(self, mach=None, target=''): |
1460 | bitbake = shutil.which('bitbake') | 1661 | bitbake = shutil.which('bitbake') |
1461 | if not bitbake: | 1662 | if not bitbake: |
1462 | return | 1663 | return |
@@ -1469,22 +1670,33 @@ class BaseConfig(object): | |||
1469 | multiconfig = "mc:%s" % multiconfig | 1670 | multiconfig = "mc:%s" % multiconfig |
1470 | 1671 | ||
1471 | if mach: | 1672 | if mach: |
1472 | cmd = 'MACHINE=%s bitbake -e %s' % (mach, multiconfig) | 1673 | cmd = 'MACHINE=%s bitbake -e %s %s' % (mach, multiconfig, target) |
1473 | else: | 1674 | else: |
1474 | cmd = 'bitbake -e %s' % multiconfig | 1675 | cmd = 'bitbake -e %s %s' % (multiconfig, target) |
1475 | 1676 | ||
1476 | logger.info('Running %s...' % cmd) | 1677 | logger.info('Running %s...' % cmd) |
1477 | return subprocess.check_output(cmd, shell=True).decode('utf-8') | 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 | |||
1478 | 1694 | ||
1479 | def load_bitbake_env(self, mach=None): | 1695 | def load_bitbake_env(self, mach=None, target=None): |
1480 | if self.bitbake_e: | 1696 | if self.bitbake_e: |
1481 | return | 1697 | return |
1482 | 1698 | ||
1483 | try: | 1699 | self.bitbake_e = self.run_bitbake_env(mach=mach, target=target) |
1484 | self.bitbake_e = self.run_bitbake_env(mach=mach) | ||
1485 | except subprocess.CalledProcessError as err: | ||
1486 | self.bitbake_e = '' | ||
1487 | logger.warning("Couldn't run 'bitbake -e' to gather environment information:\n%s" % err.output.decode('utf-8')) | ||
1488 | 1700 | ||
1489 | def validate_combos(self): | 1701 | def validate_combos(self): |
1490 | if (self.fstype in self.vmtypes) and self.kernel: | 1702 | if (self.fstype in self.vmtypes) and self.kernel: |
@@ -1514,7 +1726,7 @@ class BaseConfig(object): | |||
1514 | return result | 1726 | return result |
1515 | raise RunQemuError("Native sysroot directory %s doesn't exist" % result) | 1727 | raise RunQemuError("Native sysroot directory %s doesn't exist" % result) |
1516 | else: | 1728 | else: |
1517 | raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % cmd) | 1729 | raise RunQemuError("Can't find STAGING_BINDIR_NATIVE in '%s' output" % str(cmd)) |
1518 | 1730 | ||
1519 | 1731 | ||
1520 | def main(): | 1732 | def main(): |
@@ -1530,11 +1742,8 @@ def main(): | |||
1530 | subprocess.check_call([renice, str(os.getpid())]) | 1742 | subprocess.check_call([renice, str(os.getpid())]) |
1531 | 1743 | ||
1532 | def sigterm_handler(signum, frame): | 1744 | def sigterm_handler(signum, frame): |
1533 | logger.info("SIGTERM received") | 1745 | logger.info("Received signal: %s" % (signum)) |
1534 | os.kill(config.qemupid, signal.SIGTERM) | ||
1535 | config.cleanup() | 1746 | config.cleanup() |
1536 | # Deliberately ignore the return code of 'tput smam'. | ||
1537 | subprocess.call(["tput", "smam"]) | ||
1538 | signal.signal(signal.SIGTERM, sigterm_handler) | 1747 | signal.signal(signal.SIGTERM, sigterm_handler) |
1539 | 1748 | ||
1540 | config.check_args() | 1749 | config.check_args() |
@@ -1546,6 +1755,7 @@ def main(): | |||
1546 | config.setup_network() | 1755 | config.setup_network() |
1547 | config.setup_rootfs() | 1756 | config.setup_rootfs() |
1548 | config.setup_final() | 1757 | config.setup_final() |
1758 | config.setup_cmd() | ||
1549 | config.start_qemu() | 1759 | config.start_qemu() |
1550 | except RunQemuError as err: | 1760 | except RunQemuError as err: |
1551 | logger.error(err) | 1761 | logger.error(err) |
@@ -1555,9 +1765,8 @@ def main(): | |||
1555 | traceback.print_exc() | 1765 | traceback.print_exc() |
1556 | return 1 | 1766 | return 1 |
1557 | finally: | 1767 | finally: |
1768 | config.cleanup_cmd() | ||
1558 | config.cleanup() | 1769 | config.cleanup() |
1559 | # Deliberately ignore the return code of 'tput smam'. | ||
1560 | subprocess.call(["tput", "smam"]) | ||
1561 | 1770 | ||
1562 | if __name__ == "__main__": | 1771 | if __name__ == "__main__": |
1563 | sys.exit(main()) | 1772 | sys.exit(main()) |