1#
2# Copyright (C) 2015 Intel Corporation
3#
4# SPDX-License-Identifier: MIT
5#
6
7# This module provides a class for starting qemu images of poky tiny.
8# It's used by testimage.bbclass.
9
10import subprocess
11import os
12import time
13import signal
14import re
15import socket
16import select
17import bb
18from .qemurunner import QemuRunner
19
20class QemuTinyRunner(QemuRunner):
21
22    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, kernel, boottime, logger, tmpfsdir=None):
23
24        # Popen object for runqemu
25        self.runqemu = None
26        # pid of the qemu process that runqemu will start
27        self.qemupid = None
28        # target ip - from the command line
29        self.ip = None
30        # host ip - where qemu is running
31        self.server_ip = None
32
33        self.machine = machine
34        self.rootfs = rootfs
35        self.display = display
36        self.tmpdir = tmpdir
37        self.deploy_dir_image = deploy_dir_image
38        self.logfile = logfile
39        self.boottime = boottime
40        self.tmpfsdir = tmpfsdir
41
42        self.runqemutime = 60
43        self.socketfile = "console.sock"
44        self.server_socket = None
45        self.kernel = kernel
46        self.logger = logger
47
48
49    def create_socket(self):
50        tries = 3
51        while tries > 0:
52            try:
53                self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
54                self.server_socket.connect(self.socketfile)
55                bb.note("Created listening socket for qemu serial console.")
56                tries = 0
57            except socket.error as msg:
58                self.server_socket.close()
59                bb.fatal("Failed to create listening socket.")
60                tries -= 1
61
62    def log(self, msg):
63        if self.logfile:
64            with open(self.logfile, "a") as f:
65                f.write("%s" % msg)
66
67    def start(self, qemuparams = None, ssh=True, extra_bootparams=None, runqemuparams='', discard_writes=True):
68
69        if self.display:
70            os.environ["DISPLAY"] = self.display
71        else:
72            bb.error("To start qemu I need a X desktop, please set DISPLAY correctly (e.g. DISPLAY=:1)")
73            return False
74        if not os.path.exists(self.rootfs):
75            bb.error("Invalid rootfs %s" % self.rootfs)
76            return False
77        if not os.path.exists(self.tmpdir):
78            bb.error("Invalid TMPDIR path %s" % self.tmpdir)
79            return False
80        else:
81            os.environ["OE_TMPDIR"] = self.tmpdir
82        if not os.path.exists(self.deploy_dir_image):
83            bb.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
84            return False
85        else:
86            os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image
87        if self.tmpfsdir:
88            env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir
89
90
91        # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact
92        # badly with screensavers.
93        os.environ["QEMU_DONT_GRAB"] = "1"
94        self.qemuparams = '--append "root=/dev/ram0 console=ttyS0" -nographic -serial unix:%s,server,nowait' % self.socketfile
95
96        launch_cmd = 'qemu-system-i386 -kernel %s -initrd %s %s' % (self.kernel, self.rootfs, self.qemuparams)
97        self.runqemu = subprocess.Popen(launch_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,preexec_fn=os.setpgrp)
98
99        bb.note("runqemu started, pid is %s" % self.runqemu.pid)
100        bb.note("waiting at most %s seconds for qemu pid" % self.runqemutime)
101        endtime = time.time() + self.runqemutime
102        while not self.is_alive() and time.time() < endtime:
103            time.sleep(1)
104
105        if self.is_alive():
106            bb.note("qemu started - qemu procces pid is %s" % self.qemupid)
107            self.create_socket()
108        else:
109            bb.note("Qemu pid didn't appeared in %s seconds" % self.runqemutime)
110            output = self.runqemu.stdout
111            self.stop()
112            bb.note("Output from runqemu:\n%s" % output.read().decode("utf-8"))
113            return False
114
115        return self.is_alive()
116
117    def run_serial(self, command, timeout=60):
118        self.server_socket.sendall(command+'\n')
119        data = ''
120        status = 0
121        stopread = False
122        endtime = time.time()+timeout
123        while time.time()<endtime and not stopread:
124                try:
125                        sread, _, _ = select.select([self.server_socket],[],[],1)
126                except InterruptedError:
127                        continue
128                for sock in sread:
129                        answer = sock.recv(1024)
130                        if answer:
131                                data += answer
132                        else:
133                                sock.close()
134                                stopread = True
135        if not data:
136            status = 1
137        if not stopread:
138            data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
139        return (status, str(data))
140
141    def find_child(self,parent_pid):
142        #
143        # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd]
144        #
145        ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,pri,ni,command'], stdout=subprocess.PIPE).communicate()[0]
146        processes = ps.decode("utf-8").split('\n')
147        nfields = len(processes[0].split()) - 1
148        pids = {}
149        commands = {}
150        for row in processes[1:]:
151            data = row.split(None, nfields)
152            if len(data) != 3:
153                continue
154            if data[1] not in pids:
155                pids[data[1]] = []
156
157            pids[data[1]].append(data[0])
158            commands[data[0]] = data[2]
159
160        if parent_pid not in pids:
161            return []
162
163        parents = []
164        newparents = pids[parent_pid]
165        while newparents:
166            next = []
167            for p in newparents:
168                if p in pids:
169                    for n in pids[p]:
170                        if n not in parents and n not in next:
171                            next.append(n)
172                if p not in parents:
173                    parents.append(p)
174                    newparents = next
175        #print("Children matching %s:" % str(parents))
176        for p in parents:
177            # Need to be careful here since runqemu runs "ldd qemu-system-xxxx"
178            # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx"
179            basecmd = commands[p].split()[0]
180            basecmd = os.path.basename(basecmd)
181            if "qemu-system" in basecmd and "-serial unix" in commands[p]:
182                return [int(p),commands[p]]
183