Exemple #1
0
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image,
                 logfile, boottime, dump_dir, dump_host_cmds, use_kvm):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm

        self.runqemutime = 60
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
                 use_kvm, logger, use_slirp=False):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_slirp = use_slirp
        self.msg = ''

        self.runqemutime = 120
        self.qemu_pidfile = 'pidfile_'+str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

        self.logger = logger
Exemple #3
0
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
                 use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False, workdir=None, tmpfsdir=None):

        # Popen object for runqemu
        self.runqemu = None
        self.runqemu_exited = False
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_ovmf = use_ovmf
        self.use_slirp = use_slirp
        self.serial_ports = serial_ports
        self.msg = ''
        self.boot_patterns = boot_patterns
        self.tmpfsdir = tmpfsdir

        self.runqemutime = 120
        if not workdir:
            workdir = os.getcwd()
        self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
        self.monitorpipe = None

        self.logger = logger

        # Enable testing other OS's
        # Set commands for target communication, and default to Linux ALWAYS
        # Other OS's or baremetal applications need to provide their
        # own implementation passing it through QemuRunner's constructor
        # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag]
        # provided variables, where <flag> is one of the mentioned below.
        accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished']
        default_boot_patterns = defaultdict(str)
        # Default to the usual paterns used to communicate with the target
        default_boot_patterns['search_reached_prompt'] = b' login:'******'send_login_user'] = '******'
        default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
        default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"

        # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
        for pattern in accepted_patterns:
            if not self.boot_patterns[pattern]:
                self.boot_patterns[pattern] = default_boot_patterns[pattern]
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
                 use_kvm, logger, use_slirp=False):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_slirp = use_slirp
        self.msg = ''

        self.runqemutime = 120
        self.qemu_pidfile = 'pidfile_'+str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

        self.logger = logger
Exemple #5
0
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None

        self.runqemutime = 60
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
class QemuRunner:

    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
                 use_kvm, logger, use_slirp=False):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_slirp = use_slirp
        self.msg = ''

        self.runqemutime = 120
        self.qemu_pidfile = 'pidfile_'+str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

        self.logger = logger

    def create_socket(self):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setblocking(0)
            sock.bind(("127.0.0.1",0))
            sock.listen(2)
            port = sock.getsockname()[1]
            self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
            return (sock, port)

        except socket.error:
            sock.close()
            raise

    def log(self, msg):
        if self.logfile:
            # It is needed to sanitize the data received from qemu
            # because is possible to have control characters
            msg = msg.decode("utf-8", errors='ignore')
            msg = re_control_char.sub('', msg)
            self.msg += msg
            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
                f.write("%s" % msg)

    def getOutput(self, o):
        import fcntl
        fl = fcntl.fcntl(o, fcntl.F_GETFL)
        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return os.read(o.fileno(), 1000000).decode("utf-8")


    def handleSIGCHLD(self, signum, frame):
        if self.runqemu and self.runqemu.poll():
            if self.runqemu.returncode:
                self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode)
                self.logger.debug("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout))
                self.stop()
                self._dump_host()
                raise SystemExit

    def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True):
        env = os.environ.copy()
        if self.display:
            env["DISPLAY"] = self.display
            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
            # interact badly with screensavers.
            env["QEMU_DONT_GRAB"] = "1"
        if not os.path.exists(self.rootfs):
            self.logger.error("Invalid rootfs %s" % self.rootfs)
            return False
        if not os.path.exists(self.tmpdir):
            self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
            return False
        else:
            env["OE_TMPDIR"] = self.tmpdir
        if not os.path.exists(self.deploy_dir_image):
            self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
            return False
        else:
            env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image

        if not launch_cmd:
            launch_cmd = 'runqemu %s %s ' % ('snapshot' if discard_writes else '', runqemuparams)
            if self.use_kvm:
                self.logger.debug('Using kvm for runqemu')
                launch_cmd += ' kvm'
            else:
                self.logger.debug('Not using kvm for runqemu')
            if not self.display:
                launch_cmd += ' nographic'
            if self.use_slirp:
                launch_cmd += ' slirp'
            launch_cmd += ' %s %s' % (self.machine, self.rootfs)

        return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)

    def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
        try:
            threadsock, threadport = self.create_socket()
            self.server_socket, self.serverport = self.create_socket()
        except socket.error as msg:
            self.logger.error("Failed to create listening socket: %s" % msg[1])
            return False

        bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1'
        if extra_bootparams:
            bootparams = bootparams + ' ' + extra_bootparams

        # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
        # and analyze descendents in order to determine it.
        if os.path.exists(self.qemu_pidfile):
            os.remove(self.qemu_pidfile)
        self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1} -pidfile {2}"'.format(bootparams, threadport, self.qemu_pidfile)
        if qemuparams:
            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'

        launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams)

        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)

        self.logger.debug('launchcmd=%s'%(launch_cmd))

        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
        # blocking at the end of the runqemu script when using this within
        # oe-selftest (this makes stty error out immediately). There ought
        # to be a proper fix but this will suffice for now.
        self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env)
        output = self.runqemu.stdout

        #
        # We need the preexec_fn above so that all runqemu processes can easily be killed
        # (by killing their process group). This presents a problem if this controlling
        # process itself is killed however since those processes don't notice the death
        # of the parent and merrily continue on.
        #
        # Rather than hack runqemu to deal with this, we add something here instead.
        # Basically we fork off another process which holds an open pipe to the parent
        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
        # the process group. This is like pctrl's PDEATHSIG but for a process group
        # rather than a single process.
        #
        r, w = os.pipe()
        self.monitorpid = os.fork()
        if self.monitorpid:
            os.close(r)
            self.monitorpipe = os.fdopen(w, "w")
        else:
            # child process
            os.setpgrp()
            os.close(w)
            r = os.fdopen(r)
            x = r.read()
            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
            sys.exit(0)

        self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
        self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
                          (self.runqemutime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.runqemutime
        while not self.is_alive() and time.time() < endtime:
            if self.runqemu.poll():
                if self.runqemu.returncode:
                    # No point waiting any longer
                    self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode)
                    self._dump_host()
                    self.stop()
                    self.logger.debug("Output from runqemu:\n%s" % self.getOutput(output))
                    return False
            time.sleep(0.5)

        if not self.is_alive():
            self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
                              (self.runqemutime, time.strftime("%D %H:%M:%S")))
            # Dump all processes to help us to figure out what is going on...
            ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0]
            processes = ps.decode("utf-8")
            self.logger.debug("Running processes:\n%s" % processes)
            self._dump_host()
            self.stop()
            op = self.getOutput(output)
            if op:
                self.logger.error("Output from runqemu:\n%s" % op)
            else:
                self.logger.error("No output from runqemu.\n")
            return False

        # We are alive: qemu is running
        out = self.getOutput(output)
        netconf = False # network configuration is not required by default
        self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" %
                          (time.time() - (endtime - self.runqemutime),
                           self.qemupid, time.strftime("%D %H:%M:%S")))
        if get_ip:
            cmdline = ''
            with open('/proc/%s/cmdline' % self.qemupid) as p:
                cmdline = p.read()
                # It is needed to sanitize the data received
                # because is possible to have control characters
                cmdline = re_control_char.sub(' ', cmdline)
            try:
                if self.use_slirp:
                    tcp_ports = cmdline.split("hostfwd=tcp::")[1]
                    host_port = tcp_ports[:tcp_ports.find('-')]
                    self.ip = "localhost:%s" % host_port
                else:
                    ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
                    self.ip = ips[0]
                    self.server_ip = ips[1]
                self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
            except (IndexError, ValueError):
                # Try to get network configuration from runqemu output
                match = re.match(r'.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*',
                                 out, re.MULTILINE|re.DOTALL)
                if match:
                    self.ip, self.server_ip, self.netmask = match.groups()
                    # network configuration is required as we couldn't get it
                    # from the runqemu command line, so qemu doesn't run kernel
                    # and guest networking is not configured
                    netconf = True
                else:
                    self.logger.error("Couldn't get ip from qemu command line and runqemu output! "
                                 "Here is the qemu command line used:\n%s\n"
                                 "and output from runqemu:\n%s" % (cmdline, out))
                    self._dump_host()
                    self.stop()
                    return False

        self.logger.debug("Target IP: %s" % self.ip)
        self.logger.debug("Server IP: %s" % self.server_ip)

        self.thread = LoggingThread(self.log, threadsock, self.logger)
        self.thread.start()
        if not self.thread.connection_established.wait(self.boottime):
            self.logger.error("Didn't receive a console connection from qemu. "
                         "Here is the qemu command line used:\n%s\nand "
                         "output from runqemu:\n%s" % (cmdline, out))
            self.stop_thread()
            return False

        self.logger.debug("Output from runqemu:\n%s", out)
        self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
                          (self.boottime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.boottime
        socklist = [self.server_socket]
        reachedlogin = False
        stopread = False
        qemusock = None
        bootlog = b''
        data = b''
        while time.time() < endtime and not stopread:
            try:
                sread, swrite, serror = select.select(socklist, [], [], 5)
            except InterruptedError:
                continue
            for sock in sread:
                if sock is self.server_socket:
                    qemusock, addr = self.server_socket.accept()
                    qemusock.setblocking(0)
                    socklist.append(qemusock)
                    socklist.remove(self.server_socket)
                    self.logger.debug("Connection from %s:%s" % addr)
                else:
                    data = data + sock.recv(1024)
                    if data:
                        bootlog += data
                        data = b''
                        if b' login:'******'t reach login banner in %d seconds (%s)" %
                                  (self.boottime, time.strftime("%D %H:%M:%S")))
            tail = lambda l: "\n".join(l.splitlines()[-25:])
            # in case bootlog is empty, use tail qemu log store at self.msg
            lines = tail(bootlog if bootlog else self.msg)
            self.logger.debug("Last 25 lines of text:\n%s" % lines)
            self.logger.debug("Check full boot log: %s" % self.logfile)
            self._dump_host()
            self.stop()
            return False

        # If we are not able to login the tests can continue
        try:
            (status, output) = self.run_serial("root\n", raw=True)
            if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
                self.logged = True
                self.logger.debug("Logged as root in serial console")
                if netconf:
                    # configure guest networking
                    cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask)
                    output = self.run_serial(cmd, raw=True)[1]
                    if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
                        self.logger.debug("configured ip address %s", self.ip)
                    else:
                        self.logger.debug("Couldn't configure guest networking")
            else:
                self.logger.debug("Couldn't login into serial console"
                            " as root using blank password")
        except:
            self.logger.debug("Serial console failed while trying to login")
        return True

    def stop(self):
        if hasattr(self, "origchldhandler"):
            signal.signal(signal.SIGCHLD, self.origchldhandler)
        self.stop_thread()
        self.stop_qemu_system()
        if self.runqemu:
            if hasattr(self, "monitorpid"):
                os.kill(self.monitorpid, signal.SIGKILL)
                self.logger.debug("Sending SIGTERM to runqemu")
                try:
                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
                except OSError as e:
                    if e.errno != errno.ESRCH:
                        raise
            endtime = time.time() + self.runqemutime
            while self.runqemu.poll() is None and time.time() < endtime:
                time.sleep(1)
            if self.runqemu.poll() is None:
                self.logger.debug("Sending SIGKILL to runqemu")
                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
            self.runqemu = None
        if hasattr(self, 'server_socket') and self.server_socket:
            self.server_socket.close()
            self.server_socket = None
        self.qemupid = None
        self.ip = None
        if os.path.exists(self.qemu_pidfile):
            os.remove(self.qemu_pidfile)

    def stop_qemu_system(self):
        if self.qemupid:
            try:
                # qemu-system behaves well and a SIGTERM is enough
                os.kill(self.qemupid, signal.SIGTERM)
            except ProcessLookupError as e:
                self.logger.warning('qemu-system ended unexpectedly')

    def stop_thread(self):
        if self.thread and self.thread.is_alive():
            self.thread.stop()
            self.thread.join()

    def restart(self, qemuparams = None):
        self.logger.debug("Restarting qemu process")
        if self.runqemu.poll() is None:
            self.stop()
        if self.start(qemuparams):
            return True
        return False

    def is_alive(self):
        if not self.runqemu or self.runqemu.poll() is not None:
            return False
        if os.path.isfile(self.qemu_pidfile):
            f = open(self.qemu_pidfile, 'r')
            qemu_pid = f.read()
            f.close()
            qemupid = int(qemu_pid)
            if os.path.exists("/proc/" + str(qemupid)):
                self.qemupid = qemupid
                return True
        return False

    def run_serial(self, command, raw=False, timeout=5):
        # We assume target system have echo to get command status
        if not raw:
            command = "%s; echo $?\n" % command

        data = ''
        status = 0
        self.server_socket.sendall(command.encode('utf-8'))
        start = time.time()
        end = start + timeout
        while True:
            now = time.time()
            if now >= end:
                data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
                break
            try:
                sread, _, _ = select.select([self.server_socket],[],[], end - now)
            except InterruptedError:
                continue
            if sread:
                answer = self.server_socket.recv(1024)
                if answer:
                    data += answer.decode('utf-8')
                    # Search the prompt to stop
                    if re.search(r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data):
                        break
                else:
                    raise Exception("No data on serial console socket")

        if data:
            if raw:
                status = 1
            else:
                # Remove first line (command line) and last line (prompt)
                data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
                index = data.rfind('\r\n')
                if index == -1:
                    status_cmd = data
                    data = ""
                else:
                    status_cmd = data[index+2:]
                    data = data[:index]
                if (status_cmd == "0"):
                    status = 1
        return (status, str(data))


    def _dump_host(self):
        self.host_dumper.create_dir("qemu")
        self.logger.warning("Qemu ended unexpectedly, dump data from host"
                " is in %s" % self.host_dumper.dump_dir)
        self.host_dumper.dump_host()
Exemple #7
0
class QemuRunner:
    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image,
                 logfile, boottime, dump_dir, dump_host_cmds, use_kvm):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm

        self.runqemutime = 60
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

    def create_socket(self):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setblocking(0)
            sock.bind(("127.0.0.1", 0))
            sock.listen(2)
            port = sock.getsockname()[1]
            logger.info(
                "Created listening socket for qemu serial console on: 127.0.0.1:%s"
                % port)
            return (sock, port)

        except socket.error:
            sock.close()
            raise

    def log(self, msg):
        if self.logfile:
            # It is needed to sanitize the data received from qemu
            # because is possible to have control characters
            msg = msg.decode("utf-8")
            msg = re_control_char.sub('', msg)
            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
                f.write("%s" % msg)

    def getOutput(self, o):
        import fcntl
        fl = fcntl.fcntl(o, fcntl.F_GETFL)
        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return os.read(o.fileno(), 1000000).decode("utf-8")

    def handleSIGCHLD(self, signum, frame):
        if self.runqemu and self.runqemu.poll():
            if self.runqemu.returncode:
                logger.info('runqemu exited with code %d' %
                            self.runqemu.returncode)
                logger.info("Output from runqemu:\n%s" %
                            self.getOutput(self.runqemu.stdout))
                self.stop()
                self._dump_host()
                raise SystemExit

    def start(self, qemuparams=None, get_ip=True, extra_bootparams=None):
        if self.display:
            os.environ["DISPLAY"] = self.display
            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
            # interact badly with screensavers.
            os.environ["QEMU_DONT_GRAB"] = "1"
        if not os.path.exists(self.rootfs):
            logger.error("Invalid rootfs %s" % self.rootfs)
            return False
        if not os.path.exists(self.tmpdir):
            logger.error("Invalid TMPDIR path %s" % self.tmpdir)
            return False
        else:
            os.environ["OE_TMPDIR"] = self.tmpdir
        if not os.path.exists(self.deploy_dir_image):
            logger.error("Invalid DEPLOY_DIR_IMAGE path %s" %
                         self.deploy_dir_image)
            return False
        else:
            os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image

        try:
            threadsock, threadport = self.create_socket()
            self.server_socket, self.serverport = self.create_socket()
        except socket.error as msg:
            logger.error("Failed to create listening socket: %s" % msg[1])
            return False

        bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1'
        if extra_bootparams:
            bootparams = bootparams + ' ' + extra_bootparams

        self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1}"'.format(
            bootparams, threadport)
        if not self.display:
            self.qemuparams = 'nographic ' + self.qemuparams
        if qemuparams:
            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'

        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)

        launch_cmd = 'runqemu '
        if self.use_kvm:
            logger.info('Using kvm for runqemu')
            launch_cmd += 'kvm '
        else:
            logger.info('Not using kvm for runqemu')
        launch_cmd += 'tcpserial=%s %s %s %s' % (self.serverport, self.machine,
                                                 self.rootfs, self.qemuparams)
        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
        # blocking at the end of the runqemu script when using this within
        # oe-selftest (this makes stty error out immediately). There ought
        # to be a proper fix but this will suffice for now.
        self.runqemu = subprocess.Popen(launch_cmd,
                                        shell=True,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.STDOUT,
                                        stdin=subprocess.PIPE,
                                        preexec_fn=os.setpgrp)
        output = self.runqemu.stdout

        #
        # We need the preexec_fn above so that all runqemu processes can easily be killed
        # (by killing their process group). This presents a problem if this controlling
        # process itself is killed however since those processes don't notice the death
        # of the parent and merrily continue on.
        #
        # Rather than hack runqemu to deal with this, we add something here instead.
        # Basically we fork off another process which holds an open pipe to the parent
        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
        # the process group. This is like pctrl's PDEATHSIG but for a process group
        # rather than a single process.
        #
        r, w = os.pipe()
        self.monitorpid = os.fork()
        if self.monitorpid:
            os.close(r)
            self.monitorpipe = os.fdopen(w, "w")
        else:
            # child process
            os.setpgrp()
            os.close(w)
            r = os.fdopen(r)
            x = r.read()
            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
            sys.exit(0)

        logger.info("runqemu started, pid is %s" % self.runqemu.pid)
        logger.info("waiting at most %s seconds for qemu pid" %
                    self.runqemutime)
        endtime = time.time() + self.runqemutime
        while not self.is_alive() and time.time() < endtime:
            if self.runqemu.poll():
                if self.runqemu.returncode:
                    # No point waiting any longer
                    logger.info('runqemu exited with code %d' %
                                self.runqemu.returncode)
                    self._dump_host()
                    self.stop()
                    logger.info("Output from runqemu:\n%s" %
                                self.getOutput(output))
                    return False
            time.sleep(1)

        if self.is_alive():
            logger.info("qemu started - qemu procces pid is %s" % self.qemupid)
            if get_ip:
                cmdline = ''
                with open('/proc/%s/cmdline' % self.qemupid) as p:
                    cmdline = p.read()
                    # It is needed to sanitize the data received
                    # because is possible to have control characters
                    cmdline = re_control_char.sub('', cmdline)
                try:
                    ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})",
                                     cmdline.split("ip=")[1])
                    if not ips or len(ips) != 3:
                        raise ValueError
                    else:
                        self.ip = ips[0]
                        self.server_ip = ips[1]
                except (IndexError, ValueError):
                    logger.info(
                        "Couldn't get ip from qemu process arguments! Here is the qemu command line used:\n%s\nand output from runqemu:\n%s"
                        % (cmdline, self.getOutput(output)))
                    self._dump_host()
                    self.stop()
                    return False
                logger.info("qemu cmdline used:\n{}".format(cmdline))
                logger.info("Target IP: %s" % self.ip)
                logger.info("Server IP: %s" % self.server_ip)

            self.thread = LoggingThread(self.log, threadsock, logger)
            self.thread.start()
            if not self.thread.connection_established.wait(self.boottime):
                logger.error("Didn't receive a console connection from qemu. "
                             "Here is the qemu command line used:\n%s\nand "
                             "output from runqemu:\n%s" %
                             (cmdline, self.getOutput(output)))
                self.stop_thread()
                return False

            logger.info("Output from runqemu:\n%s", self.getOutput(output))
            logger.info("Waiting at most %d seconds for login banner" %
                        self.boottime)
            endtime = time.time() + self.boottime
            socklist = [self.server_socket]
            reachedlogin = False
            stopread = False
            qemusock = None
            bootlog = ''
            data = b''
            while time.time() < endtime and not stopread:
                sread, swrite, serror = select.select(socklist, [], [], 5)
                for sock in sread:
                    if sock is self.server_socket:
                        qemusock, addr = self.server_socket.accept()
                        qemusock.setblocking(0)
                        socklist.append(qemusock)
                        socklist.remove(self.server_socket)
                        logger.info("Connection from %s:%s" % addr)
                    else:
                        data = data + sock.recv(1024)
                        if data:
                            try:
                                data = data.decode("utf-8",
                                                   errors="surrogateescape")
                                bootlog += data
                                data = b''
                                if re.search(".* login:"******"Reached login banner")
                            except UnicodeDecodeError:
                                continue
                        else:
                            socklist.remove(sock)
                            sock.close()
                            stopread = True

            if not reachedlogin:
                logger.info("Target didn't reached login boot in %d seconds" %
                            self.boottime)
                lines = "\n".join(bootlog.splitlines()[-25:])
                logger.info("Last 25 lines of text:\n%s" % lines)
                logger.info("Check full boot log: %s" % self.logfile)
                self._dump_host()
                self.stop()
                return False

            # If we are not able to login the tests can continue
            try:
                (status, output) = self.run_serial("root\n", raw=True)
                if re.search("root@[a-zA-Z0-9\-]+:~#", output):
                    self.logged = True
                    logger.info("Logged as root in serial console")
                else:
                    logger.info("Couldn't login into serial console"
                                " as root using blank password")
            except:
                logger.info("Serial console failed while trying to login")

        else:
            logger.info("Qemu pid didn't appeared in %s seconds" %
                        self.runqemutime)
            self._dump_host()
            self.stop()
            logger.info("Output from runqemu:\n%s" % self.getOutput(output))
            return False

        return self.is_alive()

    def stop(self):
        self.stop_thread()
        if hasattr(self, "origchldhandler"):
            signal.signal(signal.SIGCHLD, self.origchldhandler)
        if self.runqemu:
            if hasattr(self, "monitorpid"):
                os.kill(self.monitorpid, signal.SIGKILL)
                logger.info("Sending SIGTERM to runqemu")
                try:
                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
                except OSError as e:
                    if e.errno != errno.ESRCH:
                        raise
            endtime = time.time() + self.runqemutime
            while self.runqemu.poll() is None and time.time() < endtime:
                time.sleep(1)
            if self.runqemu.poll() is None:
                logger.info("Sending SIGKILL to runqemu")
                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
            self.runqemu = None
        if hasattr(self, 'server_socket') and self.server_socket:
            self.server_socket.close()
            self.server_socket = None
        self.qemupid = None
        self.ip = None

    def stop_thread(self):
        if self.thread and self.thread.is_alive():
            self.thread.stop()
            self.thread.join()

    def restart(self, qemuparams=None):
        logger.info("Restarting qemu process")
        if self.runqemu.poll() is None:
            self.stop()
        if self.start(qemuparams):
            return True
        return False

    def is_alive(self):
        if not self.runqemu:
            return False
        qemu_child = self.find_child(str(self.runqemu.pid))
        if qemu_child:
            self.qemupid = qemu_child[0]
            if os.path.exists("/proc/" + str(self.qemupid)):
                return True
        return False

    def find_child(self, parent_pid):
        #
        # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd]
        #
        ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'],
                              stdout=subprocess.PIPE).communicate()[0]
        processes = ps.decode("utf-8").split('\n')
        nfields = len(processes[0].split()) - 1
        pids = {}
        commands = {}
        for row in processes[1:]:
            data = row.split(None, nfields)
            if len(data) != 3:
                continue
            if data[1] not in pids:
                pids[data[1]] = []

            pids[data[1]].append(data[0])
            commands[data[0]] = data[2]

        if parent_pid not in pids:
            return []

        parents = []
        newparents = pids[parent_pid]
        while newparents:
            next = []
            for p in newparents:
                if p in pids:
                    for n in pids[p]:
                        if n not in parents and n not in next:
                            next.append(n)
                if p not in parents:
                    parents.append(p)
                    newparents = next
        #print("Children matching %s:" % str(parents))
        for p in parents:
            # Need to be careful here since runqemu-internal runs "ldd qemu-system-xxxx"
            # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx"
            basecmd = commands[p].split()[0]
            basecmd = os.path.basename(basecmd)
            if "qemu-system" in basecmd and "-serial tcp" in commands[p]:
                return [int(p), commands[p]]

    def run_serial(self, command, raw=False):
        # We assume target system have echo to get command status
        if not raw:
            command = "%s; echo $?\n" % command

        data = ''
        status = 0
        self.server_socket.sendall(command.encode('utf-8'))
        keepreading = True
        while keepreading:
            sread, _, _ = select.select([self.server_socket], [], [], 5)
            if sread:
                answer = self.server_socket.recv(1024)
                if answer:
                    data += answer.decode('utf-8')
                    # Search the prompt to stop
                    if re.search("[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data):
                        keepreading = False
                else:
                    raise Exception("No data on serial console socket")
            else:
                keepreading = False

        if data:
            if raw:
                status = 1
            else:
                # Remove first line (command line) and last line (prompt)
                data = data[data.find('$?\r\n') + 4:data.rfind('\r\n')]
                index = data.rfind('\r\n')
                if index == -1:
                    status_cmd = data
                    data = ""
                else:
                    status_cmd = data[index + 2:]
                    data = data[:index]
                if (status_cmd == "0"):
                    status = 1
        return (status, str(data))

    def _dump_host(self):
        self.host_dumper.create_dir("qemu")
        logger.warn("Qemu ended unexpectedly, dump data from host"
                    " is in %s" % self.host_dumper.dump_dir)
        self.host_dumper.dump_host()
Exemple #8
0
 def getHostDumper(cmds, directory):
     return HostDumper(cmds, directory)
Exemple #9
0
class QemuRunner:

    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, use_kvm):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm

        self.runqemutime = 60
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

    def create_socket(self):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setblocking(0)
            sock.bind(("127.0.0.1",0))
            sock.listen(2)
            port = sock.getsockname()[1]
            logger.info("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
            return (sock, port)

        except socket.error:
            sock.close()
            raise

    def log(self, msg):
        if self.logfile:
            # It is needed to sanitize the data received from qemu
            # because is possible to have control characters
            msg = msg.decode("utf-8")
            msg = re_control_char.sub('', msg)
            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
                f.write("%s" % msg)

    def getOutput(self, o):
        import fcntl
        fl = fcntl.fcntl(o, fcntl.F_GETFL)
        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return os.read(o.fileno(), 1000000).decode("utf-8")


    def handleSIGCHLD(self, signum, frame):
        if self.runqemu and self.runqemu.poll():
            if self.runqemu.returncode:
                logger.info('runqemu exited with code %d' % self.runqemu.returncode)
                logger.info("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout))
                self.stop()
                self._dump_host()
                raise SystemExit

    def start(self, qemuparams = None, get_ip = True, extra_bootparams = None):
        if self.display:
            os.environ["DISPLAY"] = self.display
            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
            # interact badly with screensavers.
            os.environ["QEMU_DONT_GRAB"] = "1"
        if not os.path.exists(self.rootfs):
            logger.error("Invalid rootfs %s" % self.rootfs)
            return False
        if not os.path.exists(self.tmpdir):
            logger.error("Invalid TMPDIR path %s" % self.tmpdir)
            return False
        else:
            os.environ["OE_TMPDIR"] = self.tmpdir
        if not os.path.exists(self.deploy_dir_image):
            logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
            return False
        else:
            os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image

        try:
            threadsock, threadport = self.create_socket()
            self.server_socket, self.serverport = self.create_socket()
        except socket.error as msg:
            logger.error("Failed to create listening socket: %s" % msg[1])
            return False


        bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1'
        if extra_bootparams:
            bootparams = bootparams + ' ' + extra_bootparams

        self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1}"'.format(bootparams, threadport)
        if not self.display:
            self.qemuparams = 'nographic ' + self.qemuparams
        if qemuparams:
            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'

        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)

        launch_cmd = 'runqemu snapshot '
        if self.use_kvm:
            logger.info('Using kvm for runqemu')
            launch_cmd += 'kvm '
        else:
            logger.info('Not using kvm for runqemu')
        launch_cmd += 'tcpserial=%s %s %s %s' % (self.serverport, self.machine, self.rootfs, self.qemuparams)
        logger.info('launchcmd=%s'%(launch_cmd))

        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
        # blocking at the end of the runqemu script when using this within
        # oe-selftest (this makes stty error out immediately). There ought
        # to be a proper fix but this will suffice for now.
        self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp)
        output = self.runqemu.stdout

        #
        # We need the preexec_fn above so that all runqemu processes can easily be killed
        # (by killing their process group). This presents a problem if this controlling
        # process itself is killed however since those processes don't notice the death
        # of the parent and merrily continue on.
        #
        # Rather than hack runqemu to deal with this, we add something here instead.
        # Basically we fork off another process which holds an open pipe to the parent
        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
        # the process group. This is like pctrl's PDEATHSIG but for a process group
        # rather than a single process.
        #
        r, w = os.pipe()
        self.monitorpid = os.fork()
        if self.monitorpid:
            os.close(r)
            self.monitorpipe = os.fdopen(w, "w")
        else:
            # child process
            os.setpgrp()
            os.close(w)
            r = os.fdopen(r)
            x = r.read()
            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
            sys.exit(0)

        logger.info("runqemu started, pid is %s" % self.runqemu.pid)
        logger.info("waiting at most %s seconds for qemu pid" % self.runqemutime)
        endtime = time.time() + self.runqemutime
        while not self.is_alive() and time.time() < endtime:
            if self.runqemu.poll():
                if self.runqemu.returncode:
                    # No point waiting any longer
                    logger.info('runqemu exited with code %d' % self.runqemu.returncode)
                    self._dump_host()
                    self.stop()
                    logger.info("Output from runqemu:\n%s" % self.getOutput(output))
                    return False
            time.sleep(1)

        if self.is_alive():
            logger.info("qemu started - qemu procces pid is %s" % self.qemupid)
            if get_ip:
                cmdline = ''
                with open('/proc/%s/cmdline' % self.qemupid) as p:
                    cmdline = p.read()
                    # It is needed to sanitize the data received
                    # because is possible to have control characters
                    cmdline = re_control_char.sub('', cmdline)
                try:
                    ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
                    if not ips or len(ips) != 3:
                        raise ValueError
                    else:
                        self.ip = ips[0]
                        self.server_ip = ips[1]
                except (IndexError, ValueError):
                    logger.info("Couldn't get ip from qemu process arguments! Here is the qemu command line used:\n%s\nand output from runqemu:\n%s" % (cmdline, self.getOutput(output)))
                    self._dump_host()
                    self.stop()
                    return False
                logger.info("qemu cmdline used:\n{}".format(cmdline))
                logger.info("Target IP: %s" % self.ip)
                logger.info("Server IP: %s" % self.server_ip)

            self.thread = LoggingThread(self.log, threadsock, logger)
            self.thread.start()
            if not self.thread.connection_established.wait(self.boottime):
                logger.error("Didn't receive a console connection from qemu. "
                             "Here is the qemu command line used:\n%s\nand "
                             "output from runqemu:\n%s" % (cmdline,
                                                           self.getOutput(output)))
                self.stop_thread()
                return False

            logger.info("Output from runqemu:\n%s", self.getOutput(output))
            logger.info("Waiting at most %d seconds for login banner" % self.boottime)
            endtime = time.time() + self.boottime
            socklist = [self.server_socket]
            reachedlogin = False
            stopread = False
            qemusock = None
            bootlog = ''
            data = b''
            while time.time() < endtime and not stopread:
                sread, swrite, serror = select.select(socklist, [], [], 5)
                for sock in sread:
                    if sock is self.server_socket:
                        qemusock, addr = self.server_socket.accept()
                        qemusock.setblocking(0)
                        socklist.append(qemusock)
                        socklist.remove(self.server_socket)
                        logger.info("Connection from %s:%s" % addr)
                    else:
                        data = data + sock.recv(1024)
                        if data:
                            try:
                                data = data.decode("utf-8", errors="surrogateescape")
                                bootlog += data
                                data = b''
                                if re.search(".* login:"******"Reached login banner")
                            except UnicodeDecodeError:
                                continue
                        else:
                            socklist.remove(sock)
                            sock.close()
                            stopread = True

            if not reachedlogin:
                logger.info("Target didn't reached login boot in %d seconds" % self.boottime)
                lines = "\n".join(bootlog.splitlines()[-25:])
                logger.info("Last 25 lines of text:\n%s" % lines)
                logger.info("Check full boot log: %s" % self.logfile)
                self._dump_host()
                self.stop()
                return False

            # If we are not able to login the tests can continue
            try:
                (status, output) = self.run_serial("root\n", raw=True)
                if re.search("root@[a-zA-Z0-9\-]+:~#", output):
                    self.logged = True
                    logger.info("Logged as root in serial console")
                else:
                    logger.info("Couldn't login into serial console"
                            " as root using blank password")
            except:
                logger.info("Serial console failed while trying to login")

        else:
            logger.info("Qemu pid didn't appeared in %s seconds" % self.runqemutime)
            self._dump_host()
            self.stop()
            logger.info("Output from runqemu:\n%s" % self.getOutput(output))
            return False

        return self.is_alive()

    def stop(self):
        self.stop_thread()
        if hasattr(self, "origchldhandler"):
            signal.signal(signal.SIGCHLD, self.origchldhandler)
        if self.runqemu:
            if hasattr(self, "monitorpid"):
                os.kill(self.monitorpid, signal.SIGKILL)
                logger.info("Sending SIGTERM to runqemu")
                try:
                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
                except OSError as e:
                    if e.errno != errno.ESRCH:
                        raise
            endtime = time.time() + self.runqemutime
            while self.runqemu.poll() is None and time.time() < endtime:
                time.sleep(1)
            if self.runqemu.poll() is None:
                logger.info("Sending SIGKILL to runqemu")
                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
            self.runqemu = None
        if hasattr(self, 'server_socket') and self.server_socket:
            self.server_socket.close()
            self.server_socket = None
        self.qemupid = None
        self.ip = None

    def stop_thread(self):
        if self.thread and self.thread.is_alive():
            self.thread.stop()
            self.thread.join()

    def restart(self, qemuparams = None):
        logger.info("Restarting qemu process")
        if self.runqemu.poll() is None:
            self.stop()
        if self.start(qemuparams):
            return True
        return False

    def is_alive(self):
        if not self.runqemu:
            return False
        qemu_child = self.find_child(str(self.runqemu.pid))
        if qemu_child:
            self.qemupid = qemu_child[0]
            if os.path.exists("/proc/" + str(self.qemupid)):
                return True
        return False

    def find_child(self,parent_pid):
        #
        # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd]
        #
        ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'], stdout=subprocess.PIPE).communicate()[0]
        processes = ps.decode("utf-8").split('\n')
        nfields = len(processes[0].split()) - 1
        pids = {}
        commands = {}
        for row in processes[1:]:
            data = row.split(None, nfields)
            if len(data) != 3:
                continue
            if data[1] not in pids:
                pids[data[1]] = []

            pids[data[1]].append(data[0])
            commands[data[0]] = data[2]

        if parent_pid not in pids:
            return []

        parents = []
        newparents = pids[parent_pid]
        while newparents:
            next = []
            for p in newparents:
                if p in pids:
                    for n in pids[p]:
                        if n not in parents and n not in next:
                            next.append(n)
                if p not in parents:
                    parents.append(p)
                    newparents = next
        #print("Children matching %s:" % str(parents))
        for p in parents:
            # Need to be careful here since runqemu runs "ldd qemu-system-xxxx"
            # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx"
            basecmd = commands[p].split()[0]
            basecmd = os.path.basename(basecmd)
            if "qemu-system" in basecmd and "-serial tcp" in commands[p]:
                return [int(p),commands[p]]

    def run_serial(self, command, raw=False):
        # We assume target system have echo to get command status
        if not raw:
            command = "%s; echo $?\n" % command

        data = ''
        status = 0
        self.server_socket.sendall(command.encode('utf-8'))
        keepreading = True
        while keepreading:
            sread, _, _ = select.select([self.server_socket],[],[],5)
            if sread:
                answer = self.server_socket.recv(1024)
                if answer:
                    data += answer.decode('utf-8')
                    # Search the prompt to stop
                    if re.search("[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data):
                        keepreading = False
                else:
                    raise Exception("No data on serial console socket")
            else:
                keepreading = False

        if data:
            if raw:
                status = 1
            else:
                # Remove first line (command line) and last line (prompt)
                data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
                index = data.rfind('\r\n')
                if index == -1:
                    status_cmd = data
                    data = ""
                else:
                    status_cmd = data[index+2:]
                    data = data[:index]
                if (status_cmd == "0"):
                    status = 1
        return (status, str(data))


    def _dump_host(self):
        self.host_dumper.create_dir("qemu")
        logger.warn("Qemu ended unexpectedly, dump data from host"
                " is in %s" % self.host_dumper.dump_dir)
        self.host_dumper.dump_host()
class QemuRunner:

    def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds,
                 use_kvm, logger, use_slirp=False):

        # Popen object for runqemu
        self.runqemu = None
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_slirp = use_slirp
        self.msg = ''

        self.runqemutime = 120
        self.qemu_pidfile = 'pidfile_'+str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)

        self.logger = logger

    def create_socket(self):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setblocking(0)
            sock.bind(("127.0.0.1",0))
            sock.listen(2)
            port = sock.getsockname()[1]
            self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port)
            return (sock, port)

        except socket.error:
            sock.close()
            raise

    def log(self, msg):
        if self.logfile:
            # It is needed to sanitize the data received from qemu
            # because is possible to have control characters
            msg = msg.decode("utf-8", errors='ignore')
            msg = re_control_char.sub('', msg)
            self.msg += msg
            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
                f.write("%s" % msg)

    def getOutput(self, o):
        import fcntl
        fl = fcntl.fcntl(o, fcntl.F_GETFL)
        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return os.read(o.fileno(), 1000000).decode("utf-8")


    def handleSIGCHLD(self, signum, frame):
        if self.runqemu and self.runqemu.poll():
            if self.runqemu.returncode:
                self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode)
                self.logger.debug("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout))
                self.stop()
                self._dump_host()
                raise SystemExit

    def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True):
        env = os.environ.copy()
        if self.display:
            env["DISPLAY"] = self.display
            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
            # interact badly with screensavers.
            env["QEMU_DONT_GRAB"] = "1"
        if not os.path.exists(self.rootfs):
            self.logger.error("Invalid rootfs %s" % self.rootfs)
            return False
        if not os.path.exists(self.tmpdir):
            self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
            return False
        else:
            env["OE_TMPDIR"] = self.tmpdir
        if not os.path.exists(self.deploy_dir_image):
            self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image)
            return False
        else:
            env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image

        if not launch_cmd:
            launch_cmd = 'runqemu %s %s ' % ('snapshot' if discard_writes else '', runqemuparams)
            if self.use_kvm:
                self.logger.debug('Using kvm for runqemu')
                launch_cmd += ' kvm'
            else:
                self.logger.debug('Not using kvm for runqemu')
            if not self.display:
                launch_cmd += ' nographic'
            if self.use_slirp:
                launch_cmd += ' slirp'
            launch_cmd += ' %s %s' % (self.machine, self.rootfs)

        return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env)

    def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None):
        try:
            self.threadsock, threadport = self.create_socket()
            self.server_socket, self.serverport = self.create_socket()
        except socket.error as msg:
            self.logger.error("Failed to create listening socket: %s" % msg[1])
            return False

        bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1'
        if extra_bootparams:
            bootparams = bootparams + ' ' + extra_bootparams

        # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
        # and analyze descendents in order to determine it.
        if os.path.exists(self.qemu_pidfile):
            os.remove(self.qemu_pidfile)
        self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1} -pidfile {2}"'.format(bootparams, threadport, self.qemu_pidfile)
        if qemuparams:
            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'

        launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams)

        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)

        self.logger.debug('launchcmd=%s'%(launch_cmd))

        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
        # blocking at the end of the runqemu script when using this within
        # oe-selftest (this makes stty error out immediately). There ought
        # to be a proper fix but this will suffice for now.
        self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env)
        output = self.runqemu.stdout

        #
        # We need the preexec_fn above so that all runqemu processes can easily be killed
        # (by killing their process group). This presents a problem if this controlling
        # process itself is killed however since those processes don't notice the death
        # of the parent and merrily continue on.
        #
        # Rather than hack runqemu to deal with this, we add something here instead.
        # Basically we fork off another process which holds an open pipe to the parent
        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
        # the process group. This is like pctrl's PDEATHSIG but for a process group
        # rather than a single process.
        #
        r, w = os.pipe()
        self.monitorpid = os.fork()
        if self.monitorpid:
            os.close(r)
            self.monitorpipe = os.fdopen(w, "w")
        else:
            # child process
            os.setpgrp()
            os.close(w)
            r = os.fdopen(r)
            x = r.read()
            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
            sys.exit(0)

        self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
        self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
                          (self.runqemutime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.runqemutime
        while not self.is_alive() and time.time() < endtime:
            if self.runqemu.poll():
                if self.runqemu.returncode:
                    # No point waiting any longer
                    self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode)
                    self._dump_host()
                    self.logger.debug("Output from runqemu:\n%s" % self.getOutput(output))
                    self.stop()
                    return False
            time.sleep(0.5)

        if not self.is_alive():
            self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
                              (self.runqemutime, time.strftime("%D %H:%M:%S")))
            # Dump all processes to help us to figure out what is going on...
            ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0]
            processes = ps.decode("utf-8")
            self.logger.debug("Running processes:\n%s" % processes)
            self._dump_host()
            op = self.getOutput(output)
            self.stop()
            if op:
                self.logger.error("Output from runqemu:\n%s" % op)
            else:
                self.logger.error("No output from runqemu.\n")
            return False

        # We are alive: qemu is running
        out = self.getOutput(output)
        netconf = False # network configuration is not required by default
        self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" %
                          (time.time() - (endtime - self.runqemutime),
                           self.qemupid, time.strftime("%D %H:%M:%S")))
        if get_ip:
            cmdline = ''
            with open('/proc/%s/cmdline' % self.qemupid) as p:
                cmdline = p.read()
                # It is needed to sanitize the data received
                # because is possible to have control characters
                cmdline = re_control_char.sub(' ', cmdline)
            try:
                if self.use_slirp:
                    tcp_ports = cmdline.split("hostfwd=tcp::")[1]
                    host_port = tcp_ports[:tcp_ports.find('-')]
                    self.ip = "localhost:%s" % host_port
                else:
                    ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1])
                    self.ip = ips[0]
                    self.server_ip = ips[1]
                self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
            except (IndexError, ValueError):
                # Try to get network configuration from runqemu output
                match = re.match(r'.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*',
                                 out, re.MULTILINE|re.DOTALL)
                if match:
                    self.ip, self.server_ip, self.netmask = match.groups()
                    # network configuration is required as we couldn't get it
                    # from the runqemu command line, so qemu doesn't run kernel
                    # and guest networking is not configured
                    netconf = True
                else:
                    self.logger.error("Couldn't get ip from qemu command line and runqemu output! "
                                 "Here is the qemu command line used:\n%s\n"
                                 "and output from runqemu:\n%s" % (cmdline, out))
                    self._dump_host()
                    self.stop()
                    return False

        self.logger.debug("Target IP: %s" % self.ip)
        self.logger.debug("Server IP: %s" % self.server_ip)

        self.thread = LoggingThread(self.log, self.threadsock, self.logger)
        self.thread.start()
        if not self.thread.connection_established.wait(self.boottime):
            self.logger.error("Didn't receive a console connection from qemu. "
                         "Here is the qemu command line used:\n%s\nand "
                         "output from runqemu:\n%s" % (cmdline, out))
            self.stop_thread()
            return False

        self.logger.debug("Output from runqemu:\n%s", out)
        self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
                          (self.boottime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.boottime
        socklist = [self.server_socket]
        reachedlogin = False
        stopread = False
        qemusock = None
        bootlog = b''
        data = b''
        while time.time() < endtime and not stopread:
            try:
                sread, swrite, serror = select.select(socklist, [], [], 5)
            except InterruptedError:
                continue
            for sock in sread:
                if sock is self.server_socket:
                    qemusock, addr = self.server_socket.accept()
                    qemusock.setblocking(0)
                    socklist.append(qemusock)
                    socklist.remove(self.server_socket)
                    self.logger.debug("Connection from %s:%s" % addr)
                else:
                    data = data + sock.recv(1024)
                    if data:
                        bootlog += data
                        data = b''
                        if b' login:'******'t reach login banner in %d seconds (%s)" %
                                  (self.boottime, time.strftime("%D %H:%M:%S")))
            tail = lambda l: "\n".join(l.splitlines()[-25:])
            bootlog = bootlog.decode("utf-8")
            # in case bootlog is empty, use tail qemu log store at self.msg
            lines = tail(bootlog if bootlog else self.msg)
            self.logger.debug("Last 25 lines of text:\n%s" % lines)
            self.logger.debug("Check full boot log: %s" % self.logfile)
            self._dump_host()
            self.stop()
            return False

        # If we are not able to login the tests can continue
        try:
            (status, output) = self.run_serial("root\n", raw=True)
            if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
                self.logged = True
                self.logger.debug("Logged as root in serial console")
                if netconf:
                    # configure guest networking
                    cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask)
                    output = self.run_serial(cmd, raw=True)[1]
                    if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
                        self.logger.debug("configured ip address %s", self.ip)
                    else:
                        self.logger.debug("Couldn't configure guest networking")
            else:
                self.logger.debug("Couldn't login into serial console"
                            " as root using blank password")
                self.logger.debug("The output:\n%s" % output)
        except:
            self.logger.debug("Serial console failed while trying to login")
        return True

    def stop(self):
        if hasattr(self, "origchldhandler"):
            signal.signal(signal.SIGCHLD, self.origchldhandler)
        self.stop_thread()
        self.stop_qemu_system()
        if self.runqemu:
            if hasattr(self, "monitorpid"):
                os.kill(self.monitorpid, signal.SIGKILL)
                self.logger.debug("Sending SIGTERM to runqemu")
                try:
                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
                except OSError as e:
                    if e.errno != errno.ESRCH:
                        raise
            endtime = time.time() + self.runqemutime
            while self.runqemu.poll() is None and time.time() < endtime:
                time.sleep(1)
            if self.runqemu.poll() is None:
                self.logger.debug("Sending SIGKILL to runqemu")
                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
            self.runqemu.stdin.close()
            self.runqemu.stdout.close()
            self.runqemu = None

        if hasattr(self, 'server_socket') and self.server_socket:
            self.server_socket.close()
            self.server_socket = None
        if hasattr(self, 'threadsock') and self.threadsock:
            self.threadsock.close()
            self.threadsock = None
        self.qemupid = None
        self.ip = None
        if os.path.exists(self.qemu_pidfile):
            os.remove(self.qemu_pidfile)
        if self.monitorpipe:
            self.monitorpipe.close()

    def stop_qemu_system(self):
        if self.qemupid:
            try:
                # qemu-system behaves well and a SIGTERM is enough
                os.kill(self.qemupid, signal.SIGTERM)
            except ProcessLookupError as e:
                self.logger.warning('qemu-system ended unexpectedly')

    def stop_thread(self):
        if self.thread and self.thread.is_alive():
            self.thread.stop()
            self.thread.join()

    def restart(self, qemuparams = None):
        self.logger.debug("Restarting qemu process")
        if self.runqemu.poll() is None:
            self.stop()
        if self.start(qemuparams):
            return True
        return False

    def is_alive(self):
        if not self.runqemu or self.runqemu.poll() is not None:
            return False
        if os.path.isfile(self.qemu_pidfile):
            f = open(self.qemu_pidfile, 'r')
            qemu_pid = f.read()
            f.close()
            qemupid = int(qemu_pid)
            if os.path.exists("/proc/" + str(qemupid)):
                self.qemupid = qemupid
                return True
        return False

    def run_serial(self, command, raw=False, timeout=60):
        # We assume target system have echo to get command status
        if not raw:
            command = "%s; echo $?\n" % command

        data = ''
        status = 0
        self.server_socket.sendall(command.encode('utf-8'))
        start = time.time()
        end = start + timeout
        while True:
            now = time.time()
            if now >= end:
                data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
                break
            try:
                sread, _, _ = select.select([self.server_socket],[],[], end - now)
            except InterruptedError:
                continue
            if sread:
                answer = self.server_socket.recv(1024)
                if answer:
                    data += answer.decode('utf-8')
                    # Search the prompt to stop
                    if re.search(r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data):
                        break
                else:
                    raise Exception("No data on serial console socket")

        if data:
            if raw:
                status = 1
            else:
                # Remove first line (command line) and last line (prompt)
                data = data[data.find('$?\r\n')+4:data.rfind('\r\n')]
                index = data.rfind('\r\n')
                if index == -1:
                    status_cmd = data
                    data = ""
                else:
                    status_cmd = data[index+2:]
                    data = data[:index]
                if (status_cmd == "0"):
                    status = 1
        return (status, str(data))


    def _dump_host(self):
        self.host_dumper.create_dir("qemu")
        self.logger.warning("Qemu ended unexpectedly, dump data from host"
                " is in %s" % self.host_dumper.dump_dir)
        self.host_dumper.dump_host()
Exemple #11
0
class QemuRunner:
    def __init__(self,
                 machine,
                 rootfs,
                 display,
                 tmpdir,
                 deploy_dir_image,
                 logfile,
                 boottime,
                 dump_dir,
                 dump_host_cmds,
                 use_kvm,
                 logger,
                 use_slirp=False,
                 serial_ports=2,
                 boot_patterns=defaultdict(str),
                 use_ovmf=False,
                 workdir=None,
                 tmpfsdir=None):

        # Popen object for runqemu
        self.runqemu = None
        self.runqemu_exited = False
        # pid of the qemu process that runqemu will start
        self.qemupid = None
        # target ip - from the command line or runqemu output
        self.ip = None
        # host ip - where qemu is running
        self.server_ip = None
        # target ip netmask
        self.netmask = None

        self.machine = machine
        self.rootfs = rootfs
        self.display = display
        self.tmpdir = tmpdir
        self.deploy_dir_image = deploy_dir_image
        self.logfile = logfile
        self.boottime = boottime
        self.logged = False
        self.thread = None
        self.use_kvm = use_kvm
        self.use_ovmf = use_ovmf
        self.use_slirp = use_slirp
        self.serial_ports = serial_ports
        self.msg = ''
        self.boot_patterns = boot_patterns
        self.tmpfsdir = tmpfsdir

        self.runqemutime = 120
        if not workdir:
            workdir = os.getcwd()
        self.qemu_pidfile = workdir + '/pidfile_' + str(os.getpid())
        self.host_dumper = HostDumper(dump_host_cmds, dump_dir)
        self.monitorpipe = None

        self.logger = logger

        # Enable testing other OS's
        # Set commands for target communication, and default to Linux ALWAYS
        # Other OS's or baremetal applications need to provide their
        # own implementation passing it through QemuRunner's constructor
        # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag]
        # provided variables, where <flag> is one of the mentioned below.
        accepted_patterns = [
            'search_reached_prompt', 'send_login_user',
            'search_login_succeeded', 'search_cmd_finished'
        ]
        default_boot_patterns = defaultdict(str)
        # Default to the usual paterns used to communicate with the target
        default_boot_patterns['search_reached_prompt'] = b' login:'******'send_login_user'] = '******'
        default_boot_patterns[
            'search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#"
        default_boot_patterns[
            'search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#"

        # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
        for pattern in accepted_patterns:
            if not self.boot_patterns[pattern]:
                self.boot_patterns[pattern] = default_boot_patterns[pattern]

    def create_socket(self):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setblocking(0)
            sock.bind(("127.0.0.1", 0))
            sock.listen(2)
            port = sock.getsockname()[1]
            self.logger.debug(
                "Created listening socket for qemu serial console on: 127.0.0.1:%s"
                % port)
            return (sock, port)

        except socket.error:
            sock.close()
            raise

    def log(self, msg):
        if self.logfile:
            # It is needed to sanitize the data received from qemu
            # because is possible to have control characters
            msg = msg.decode("utf-8", errors='ignore')
            msg = re_control_char.sub('', msg)
            self.msg += msg
            with codecs.open(self.logfile, "a", encoding="utf-8") as f:
                f.write("%s" % msg)

    def getOutput(self, o):
        import fcntl
        fl = fcntl.fcntl(o, fcntl.F_GETFL)
        fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return os.read(o.fileno(), 1000000).decode("utf-8")

    def handleSIGCHLD(self, signum, frame):
        if self.runqemu and self.runqemu.poll():
            if self.runqemu.returncode:
                self.logger.error('runqemu exited with code %d' %
                                  self.runqemu.returncode)
                self.logger.error('Output from runqemu:\n%s' %
                                  self.getOutput(self.runqemu.stdout))
                self.stop()
                self._dump_host()

    def start(self,
              qemuparams=None,
              get_ip=True,
              extra_bootparams=None,
              runqemuparams='',
              launch_cmd=None,
              discard_writes=True):
        env = os.environ.copy()
        if self.display:
            env["DISPLAY"] = self.display
            # Set this flag so that Qemu doesn't do any grabs as SDL grabs
            # interact badly with screensavers.
            env["QEMU_DONT_GRAB"] = "1"
        if not os.path.exists(self.rootfs):
            self.logger.error("Invalid rootfs %s" % self.rootfs)
            return False
        if not os.path.exists(self.tmpdir):
            self.logger.error("Invalid TMPDIR path %s" % self.tmpdir)
            return False
        else:
            env["OE_TMPDIR"] = self.tmpdir
        if not os.path.exists(self.deploy_dir_image):
            self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" %
                              self.deploy_dir_image)
            return False
        else:
            env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image

        if self.tmpfsdir:
            env["RUNQEMU_TMPFS_DIR"] = self.tmpfsdir

        if not launch_cmd:
            launch_cmd = 'runqemu %s' % ('snapshot' if discard_writes else '')
            if self.use_kvm:
                self.logger.debug('Using kvm for runqemu')
                launch_cmd += ' kvm'
            else:
                self.logger.debug('Not using kvm for runqemu')
            if not self.display:
                launch_cmd += ' nographic'
            if self.use_slirp:
                launch_cmd += ' slirp'
            if self.use_ovmf:
                launch_cmd += ' ovmf'
            launch_cmd += ' %s %s %s' % (runqemuparams, self.machine,
                                         self.rootfs)

        return self.launch(launch_cmd,
                           qemuparams=qemuparams,
                           get_ip=get_ip,
                           extra_bootparams=extra_bootparams,
                           env=env)

    def launch(self,
               launch_cmd,
               get_ip=True,
               qemuparams=None,
               extra_bootparams=None,
               env=None):
        # use logfile to determine the recipe-sysroot-native path and
        # then add in the site-packages path components and add that
        # to the python sys.path so qmp.py can be found.
        python_path = os.path.dirname(os.path.dirname(self.logfile))
        python_path += "/recipe-sysroot-native/usr/lib/python3.9/site-packages"
        sys.path.append(python_path)
        importlib.invalidate_caches()
        try:
            qmp = importlib.import_module("qmp")
        except:
            self.logger.error(
                "qemurunner: qmp.py missing, please ensure it's installed")
            return False
        # Path relative to tmpdir used as cwd for qemu below to avoid unix socket path length issues
        qmp_file = "." + next(tempfile._get_candidate_names())
        qmp_param = ' -S -qmp unix:./%s,server,wait' % (qmp_file)
        qmp_port = self.tmpdir + "/" + qmp_file

        try:
            if self.serial_ports >= 2:
                self.threadsock, threadport = self.create_socket()
            self.server_socket, self.serverport = self.create_socket()
        except socket.error as msg:
            self.logger.error("Failed to create listening socket: %s" % msg[1])
            return False

        bootparams = ' printk.time=1'
        if extra_bootparams:
            bootparams = bootparams + ' ' + extra_bootparams

        # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes
        # and analyze descendents in order to determine it.
        if os.path.exists(self.qemu_pidfile):
            os.remove(self.qemu_pidfile)
        self.qemuparams = 'bootparams="{0}" qemuparams="-pidfile {1} {2}"'.format(
            bootparams, self.qemu_pidfile, qmp_param)

        if qemuparams:
            self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"'

        if self.serial_ports >= 2:
            launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport,
                                                   self.qemuparams)
        else:
            launch_cmd += ' tcpserial=%s %s' % (self.serverport,
                                                self.qemuparams)

        self.origchldhandler = signal.getsignal(signal.SIGCHLD)
        signal.signal(signal.SIGCHLD, self.handleSIGCHLD)

        self.logger.debug('launchcmd=%s' % (launch_cmd))

        # FIXME: We pass in stdin=subprocess.PIPE here to work around stty
        # blocking at the end of the runqemu script when using this within
        # oe-selftest (this makes stty error out immediately). There ought
        # to be a proper fix but this will suffice for now.
        self.runqemu = subprocess.Popen(launch_cmd,
                                        shell=True,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.STDOUT,
                                        stdin=subprocess.PIPE,
                                        preexec_fn=os.setpgrp,
                                        env=env,
                                        cwd=self.tmpdir)
        output = self.runqemu.stdout

        #
        # We need the preexec_fn above so that all runqemu processes can easily be killed
        # (by killing their process group). This presents a problem if this controlling
        # process itself is killed however since those processes don't notice the death
        # of the parent and merrily continue on.
        #
        # Rather than hack runqemu to deal with this, we add something here instead.
        # Basically we fork off another process which holds an open pipe to the parent
        # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills
        # the process group. This is like pctrl's PDEATHSIG but for a process group
        # rather than a single process.
        #
        r, w = os.pipe()
        self.monitorpid = os.fork()
        if self.monitorpid:
            os.close(r)
            self.monitorpipe = os.fdopen(w, "w")
        else:
            # child process
            os.setpgrp()
            os.close(w)
            r = os.fdopen(r)
            x = r.read()
            os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
            sys.exit(0)

        self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
        self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
                          (self.runqemutime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.runqemutime
        while not self.is_alive() and time.time() < endtime:
            if self.runqemu.poll():
                if self.runqemu_exited:
                    self.logger.warning("runqemu during is_alive() test")
                    return False
                if self.runqemu.returncode:
                    # No point waiting any longer
                    self.logger.warning('runqemu exited with code %d' %
                                        self.runqemu.returncode)
                    self._dump_host()
                    self.logger.warning("Output from runqemu:\n%s" %
                                        self.getOutput(output))
                    self.stop()
                    return False
            time.sleep(0.5)

        if self.runqemu_exited:
            self.logger.warning("runqemu after timeout")
            return False

        if self.runqemu.returncode:
            self.logger.warning('runqemu exited with code %d' %
                                self.runqemu.returncode)
            return False

        if not self.is_alive():
            self.logger.warning('is_alive() failed later')
            return False

        # Create the client socket for the QEMU Monitor Control Socket
        # This will allow us to read status from Qemu if the the process
        # is still alive
        self.logger.debug("QMP Initializing to %s" % (qmp_port))
        # chdir dance for path length issues with unix sockets
        origpath = os.getcwd()
        try:
            os.chdir(os.path.dirname(qmp_port))
            try:
                self.qmp = qmp.QEMUMonitorProtocol(os.path.basename(qmp_port))
            except OSError as msg:
                self.logger.warning(
                    "Failed to initialize qemu monitor socket: %s File: %s" %
                    (msg, msg.filename))
                return False

            self.logger.debug("QMP Connecting to %s" % (qmp_port))
            if not os.path.exists(qmp_port) and self.is_alive():
                self.logger.debug(
                    "QMP Port does not exist waiting for it to be created")
                endtime = time.time() + self.runqemutime
                while not os.path.exists(qmp_port) and self.is_alive(
                ) and time.time() < endtime:
                    self.logger.info("QMP port does not exist yet!")
                    time.sleep(0.5)
                if not os.path.exists(qmp_port) and self.is_alive():
                    self.logger.warning(
                        "QMP Port still does not exist but QEMU is alive")
                    return False

            try:
                self.qmp.connect()
            except OSError as msg:
                self.logger.warning(
                    "Failed to connect qemu monitor socket: %s File: %s" %
                    (msg, msg.filename))
                return False
            except qmp.QMPConnectError as msg:
                self.logger.warning(
                    "Failed to communicate with qemu monitor: %s" % (msg))
                return False
        finally:
            os.chdir(origpath)

        # Release the qemu porcess to continue running
        self.run_monitor('cont')

        if not self.is_alive():
            self.logger.error("Qemu pid didn't appear in %s seconds (%s)" %
                              (self.runqemutime, time.strftime("%D %H:%M:%S")))

            qemu_pid = None
            if os.path.isfile(self.qemu_pidfile):
                with open(self.qemu_pidfile, 'r') as f:
                    qemu_pid = f.read().strip()

            self.logger.error(
                "Status information, poll status: %s, pidfile exists: %s, pidfile contents %s, proc pid exists %s"
                % (self.runqemu.poll(), os.path.isfile(self.qemu_pidfile),
                   str(qemu_pid), os.path.exists("/proc/" + str(qemu_pid))))

            # Dump all processes to help us to figure out what is going on...
            ps = subprocess.Popen(
                ['ps', 'axww', '-o', 'pid,ppid,pri,ni,command '],
                stdout=subprocess.PIPE).communicate()[0]
            processes = ps.decode("utf-8")
            self.logger.debug("Running processes:\n%s" % processes)
            self._dump_host()
            op = self.getOutput(output)
            self.stop()
            if op:
                self.logger.error("Output from runqemu:\n%s" % op)
            else:
                self.logger.error("No output from runqemu.\n")
            return False

        # We are alive: qemu is running
        out = self.getOutput(output)
        netconf = False  # network configuration is not required by default
        self.logger.debug(
            "qemu started in %s seconds - qemu procces pid is %s (%s)" %
            (time.time() - (endtime - self.runqemutime), self.qemupid,
             time.strftime("%D %H:%M:%S")))
        cmdline = ''
        if get_ip:
            with open('/proc/%s/cmdline' % self.qemupid) as p:
                cmdline = p.read()
                # It is needed to sanitize the data received
                # because is possible to have control characters
                cmdline = re_control_char.sub(' ', cmdline)
            try:
                if self.use_slirp:
                    tcp_ports = cmdline.split("hostfwd=tcp::")[1]
                    host_port = tcp_ports[:tcp_ports.find('-')]
                    self.ip = "localhost:%s" % host_port
                else:
                    ips = re.findall(r"((?:[0-9]{1,3}\.){3}[0-9]{1,3})",
                                     cmdline.split("ip=")[1])
                    self.ip = ips[0]
                    self.server_ip = ips[1]
                self.logger.debug("qemu cmdline used:\n{}".format(cmdline))
            except (IndexError, ValueError):
                # Try to get network configuration from runqemu output
                match = re.match(
                    r'.*Network configuration: (?:ip=)*([0-9.]+)::([0-9.]+):([0-9.]+)$.*',
                    out, re.MULTILINE | re.DOTALL)
                if match:
                    self.ip, self.server_ip, self.netmask = match.groups()
                    # network configuration is required as we couldn't get it
                    # from the runqemu command line, so qemu doesn't run kernel
                    # and guest networking is not configured
                    netconf = True
                else:
                    self.logger.error(
                        "Couldn't get ip from qemu command line and runqemu output! "
                        "Here is the qemu command line used:\n%s\n"
                        "and output from runqemu:\n%s" % (cmdline, out))
                    self._dump_host()
                    self.stop()
                    return False

        self.logger.debug("Target IP: %s" % self.ip)
        self.logger.debug("Server IP: %s" % self.server_ip)

        if self.serial_ports >= 2:
            self.thread = LoggingThread(self.log, self.threadsock, self.logger)
            self.thread.start()
            if not self.thread.connection_established.wait(self.boottime):
                self.logger.error(
                    "Didn't receive a console connection from qemu. "
                    "Here is the qemu command line used:\n%s\nand "
                    "output from runqemu:\n%s" % (cmdline, out))
                self.stop_thread()
                return False

        self.logger.debug("Output from runqemu:\n%s", out)
        self.logger.debug("Waiting at most %d seconds for login banner (%s)" %
                          (self.boottime, time.strftime("%D %H:%M:%S")))
        endtime = time.time() + self.boottime
        socklist = [self.server_socket]
        reachedlogin = False
        stopread = False
        qemusock = None
        bootlog = b''
        data = b''
        while time.time() < endtime and not stopread:
            try:
                sread, swrite, serror = select.select(socklist, [], [], 5)
            except InterruptedError:
                continue
            for sock in sread:
                if sock is self.server_socket:
                    qemusock, addr = self.server_socket.accept()
                    qemusock.setblocking(0)
                    socklist.append(qemusock)
                    socklist.remove(self.server_socket)
                    self.logger.debug("Connection from %s:%s" % addr)
                else:
                    data = data + sock.recv(1024)
                    if data:
                        bootlog += data
                        if self.serial_ports < 2:
                            # this socket has mixed console/kernel data, log it to logfile
                            self.log(data)

                        data = b''
                        if self.boot_patterns[
                                'search_reached_prompt'] in bootlog:
                            self.server_socket = qemusock
                            stopread = True
                            reachedlogin = True
                            self.logger.debug(
                                "Reached login banner in %s seconds (%s)" %
                                (time.time() - (endtime - self.boottime),
                                 time.strftime("%D %H:%M:%S")))
                    else:
                        # no need to check if reachedlogin unless we support multiple connections
                        self.logger.debug(
                            "QEMU socket disconnected before login banner reached. (%s)"
                            % time.strftime("%D %H:%M:%S"))
                        socklist.remove(sock)
                        sock.close()
                        stopread = True

        if not reachedlogin:
            if time.time() >= endtime:
                self.logger.warning(
                    "Target didn't reach login banner in %d seconds (%s)" %
                    (self.boottime, time.strftime("%D %H:%M:%S")))
            tail = lambda l: "\n".join(l.splitlines()[-25:])
            bootlog = bootlog.decode("utf-8")
            # in case bootlog is empty, use tail qemu log store at self.msg
            lines = tail(bootlog if bootlog else self.msg)
            self.logger.warning("Last 25 lines of text:\n%s" % lines)
            self.logger.warning("Check full boot log: %s" % self.logfile)
            self._dump_host()
            self.stop()
            return False

        # If we are not able to login the tests can continue
        try:
            (status,
             output) = self.run_serial(self.boot_patterns['send_login_user'],
                                       raw=True,
                                       timeout=120)
            if re.search(self.boot_patterns['search_login_succeeded'], output):
                self.logged = True
                self.logger.debug("Logged as root in serial console")
                if netconf:
                    # configure guest networking
                    cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip,
                                                                self.netmask)
                    output = self.run_serial(cmd, raw=True)[1]
                    if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
                        self.logger.debug("configured ip address %s", self.ip)
                    else:
                        self.logger.debug(
                            "Couldn't configure guest networking")
            else:
                self.logger.warning("Couldn't login into serial console"
                                    " as root using blank password")
                self.logger.warning("The output:\n%s" % output)
        except:
            self.logger.warning("Serial console failed while trying to login")
        return True

    def stop(self):
        if hasattr(self, "origchldhandler"):
            signal.signal(signal.SIGCHLD, self.origchldhandler)
        self.stop_thread()
        self.stop_qemu_system()
        if self.runqemu:
            if hasattr(self, "monitorpid"):
                os.kill(self.monitorpid, signal.SIGKILL)
                self.logger.debug("Sending SIGTERM to runqemu")
                try:
                    os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
                except OSError as e:
                    if e.errno != errno.ESRCH:
                        raise
            endtime = time.time() + self.runqemutime
            while self.runqemu.poll() is None and time.time() < endtime:
                time.sleep(1)
            if self.runqemu.poll() is None:
                self.logger.debug("Sending SIGKILL to runqemu")
                os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
            self.runqemu.stdin.close()
            self.runqemu.stdout.close()
            self.runqemu_exited = True

        if hasattr(self, 'qmp') and self.qmp:
            self.qmp.close()
            self.qmp = None
        if hasattr(self, 'server_socket') and self.server_socket:
            self.server_socket.close()
            self.server_socket = None
        if hasattr(self, 'threadsock') and self.threadsock:
            self.threadsock.close()
            self.threadsock = None
        self.qemupid = None
        self.ip = None
        if os.path.exists(self.qemu_pidfile):
            try:
                os.remove(self.qemu_pidfile)
            except FileNotFoundError as e:
                # We raced, ignore
                pass
        if self.monitorpipe:
            self.monitorpipe.close()

    def stop_qemu_system(self):
        if self.qemupid:
            try:
                # qemu-system behaves well and a SIGTERM is enough
                os.kill(self.qemupid, signal.SIGTERM)
            except ProcessLookupError as e:
                self.logger.warning('qemu-system ended unexpectedly')

    def stop_thread(self):
        if self.thread and self.thread.is_alive():
            self.thread.stop()
            self.thread.join()

    def allowexit(self):
        if self.thread:
            self.thread.allowexit()

    def restart(self, qemuparams=None):
        self.logger.warning("Restarting qemu process")
        if self.runqemu.poll() is None:
            self.stop()
        if self.start(qemuparams):
            return True
        return False

    def is_alive(self):
        if not self.runqemu or self.runqemu.poll(
        ) is not None or self.runqemu_exited:
            return False
        if os.path.isfile(self.qemu_pidfile):
            # when handling pidfile, qemu creates the file, stat it, lock it and then write to it
            # so it's possible that the file has been created but the content is empty
            pidfile_timeout = time.time() + 3
            while time.time() < pidfile_timeout:
                with open(self.qemu_pidfile, 'r') as f:
                    qemu_pid = f.read().strip()
                # file created but not yet written contents
                if not qemu_pid:
                    time.sleep(0.5)
                    continue
                else:
                    if os.path.exists("/proc/" + qemu_pid):
                        self.qemupid = int(qemu_pid)
                        return True
        return False

    def run_monitor(self, command, timeout=60):
        return self.qmp.cmd(command)

    def run_serial(self, command, raw=False, timeout=60):
        # We assume target system have echo to get command status
        if not raw:
            command = "%s; echo $?\n" % command

        data = ''
        status = 0
        self.server_socket.sendall(command.encode('utf-8'))
        start = time.time()
        end = start + timeout
        while True:
            now = time.time()
            if now >= end:
                data += "<<< run_serial(): command timed out after %d seconds without output >>>\r\n\r\n" % timeout
                break
            try:
                sread, _, _ = select.select([self.server_socket], [], [],
                                            end - now)
            except InterruptedError:
                continue
            if sread:
                answer = self.server_socket.recv(1024)
                if answer:
                    data += answer.decode('utf-8')
                    # Search the prompt to stop
                    if re.search(self.boot_patterns['search_cmd_finished'],
                                 data):
                        break
                else:
                    raise Exception("No data on serial console socket")

        if data:
            if raw:
                status = 1
            else:
                # Remove first line (command line) and last line (prompt)
                data = data[data.find('$?\r\n') + 4:data.rfind('\r\n')]
                index = data.rfind('\r\n')
                if index == -1:
                    status_cmd = data
                    data = ""
                else:
                    status_cmd = data[index + 2:]
                    data = data[:index]
                if (status_cmd == "0"):
                    status = 1
        return (status, str(data))

    def _dump_host(self):
        self.host_dumper.create_dir("qemu")
        self.logger.warning("Qemu ended unexpectedly, dump data from host"
                            " is in %s" % self.host_dumper.dump_dir)
        self.host_dumper.dump_host()