liyujie
2025-08-28 b3810562527858a3b3d98ffa6e9c9c5b0f4a9a8e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility to run a Brillo emulator programmatically.
 
Requires system.img, userdata.img and kernel to be in imagedir. If running an
arm emulator kernel.dtb (or another dtb file) must also be in imagedir.
 
WARNING: Processes created by this utility may not die unless
EmulatorManager.stop is called. Call EmulatorManager.verify_stop to
confirm process has stopped and port is free.
"""
 
import os
import time
 
import common
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils
 
 
class EmulatorManagerException(Exception):
    """Bad port, missing artifact or non-existant imagedir."""
    pass
 
 
class EmulatorManager(object):
    """Manage an instance of a device emulator.
 
    @param imagedir: directory of emulator images.
    @param port: Port number for emulator's adbd. Note this port is one higher
                 than the port in the emulator's serial number.
    @param run: Function used to execute shell commands.
    """
    def __init__(self, imagedir, port, run=utils.run):
        if not port % 2 or port < 5555 or port > 5585:
             raise EmulatorManagerException('Port must be an odd number '
                                            'between 5555 and 5585.')
        try:
            run('test -f %s' % os.path.join(imagedir, 'system.img'))
        except error.GenericHostRunError:
            raise EmulatorManagerException('Image directory must exist and '
                                           'contain emulator images.')
 
        self.port = port
        self.imagedir = imagedir
        self.run = run
 
 
    def verify_stop(self, timeout_secs=3):
        """Wait for emulator on our port to stop.
 
        @param timeout_secs: Max seconds to wait for the emulator to stop.
 
        @return: Bool - True if emulator stops.
        """
        cycles = 0
        pid = self.find()
        while pid:
            cycles += 1
            time.sleep(0.1)
            pid = self.find()
            if cycles >= timeout_secs*10 and pid:
                return False
        return True
 
 
    def _find_dtb(self):
        """Detect a dtb file in the image directory
 
        @return: Path to dtb file or None.
        """
        cmd_result = self.run('find "%s" -name "*.dtb"' % self.imagedir)
        dtb = cmd_result.stdout.split('\n')[0]
        return dtb.strip() or None
 
 
    def start(self):
        """Start an emulator with the images and port specified.
 
        If an emulator is already running on the port it will be killed.
        """
        self.force_stop()
        time.sleep(1) # Wait for port to be free
        # TODO(jgiorgi): Add support for x86 / x64 emulators
        args = [
            '-dmS', 'emulator-%s' % self.port, 'qemu-system-arm',
            '-M', 'vexpress-a9',
            '-m', '1024M',
            '-kernel', os.path.join(self.imagedir, 'kernel'),
            '-append', ('"console=ttyAMA0 ro root=/dev/sda '
                        'androidboot.hardware=qemu qemu=1 rootwait noinitrd '
                        'init=/init androidboot.selinux=enforcing"'),
            '-nographic',
            '-device', 'virtio-scsi-device,id=scsi',
            '-device', 'scsi-hd,drive=system',
            '-drive', ('file="%s,if=none,id=system,format=raw"'
                       % os.path.join(self.imagedir, 'system.img')),
            '-device', 'scsi-hd,drive=userdata',
            '-drive', ('file="%s,if=none,id=userdata,format=raw"'
                       % os.path.join(self.imagedir, 'userdata.img')),
            '-redir', 'tcp:%s::5555' % self.port,
        ]
 
        # DTB file produced and required for arm but not x86 emulators
        dtb = self._find_dtb()
        if dtb:
            args += ['-dtb', dtb]
        else:
            raise EmulatorManagerException('DTB file missing. Required for arm '
                                           'emulators.')
 
        self.run(' '.join(['screen'] + args))
 
 
    def find(self):
        """Detect the PID of a qemu process running on our port.
 
        @return: PID or None
        """
        running = self.run('netstat -nlpt').stdout
        for proc in running.split('\n'):
            if ':%s' % self.port in proc:
                process = proc.split()[-1]
                if '/' in process: # Program identified, we started and can kill
                    return process.split('/')[0]
 
 
    def stop(self, kill=False):
        """Send signal to stop emulator process.
 
        Signal is sent to any running qemu process on our port regardless of how
        it was started. Silent no-op if no running qemu processes on the port.
 
        @param kill: Send SIGKILL signal instead of SIGTERM.
        """
        pid = self.find()
        if pid:
            cmd = 'kill -9 %s' if kill else 'kill %s'
            self.run(cmd % pid)
 
 
    def force_stop(self):
        """Attempt graceful shutdown, kill if not dead after 3 seconds.
        """
        self.stop()
        if not self.verify_stop(timeout_secs=3):
            self.stop(kill=True)
        if not self.verify_stop():
            raise RuntimeError('Emulator running on port %s failed to stop.'
                               % self.port)