xref: /aosp_15_r20/development/python-packages/fastboot/device.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#
2*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2016 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker#
4*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker#
8*90c8c64dSAndroid Build Coastguard Worker#            http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker#
10*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker# limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker#
16*90c8c64dSAndroid Build Coastguard Worker"""Provides functionality to interact with a device via `fastboot`."""
17*90c8c64dSAndroid Build Coastguard Worker
18*90c8c64dSAndroid Build Coastguard Workerimport os
19*90c8c64dSAndroid Build Coastguard Workerimport re
20*90c8c64dSAndroid Build Coastguard Workerimport subprocess
21*90c8c64dSAndroid Build Coastguard Worker
22*90c8c64dSAndroid Build Coastguard Worker
23*90c8c64dSAndroid Build Coastguard Workerclass FastbootError(Exception):
24*90c8c64dSAndroid Build Coastguard Worker    """Something went wrong interacting with fastboot."""
25*90c8c64dSAndroid Build Coastguard Worker
26*90c8c64dSAndroid Build Coastguard Worker
27*90c8c64dSAndroid Build Coastguard Workerclass FastbootDevice(object):
28*90c8c64dSAndroid Build Coastguard Worker    """Class to interact with a fastboot device."""
29*90c8c64dSAndroid Build Coastguard Worker
30*90c8c64dSAndroid Build Coastguard Worker    # Prefix for INFO-type messages when printed by fastboot. If we want
31*90c8c64dSAndroid Build Coastguard Worker    # to parse the output from an INFO message we need to strip this off.
32*90c8c64dSAndroid Build Coastguard Worker    INFO_PREFIX = '(bootloader) '
33*90c8c64dSAndroid Build Coastguard Worker
34*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path='fastboot'):
35*90c8c64dSAndroid Build Coastguard Worker        """Initialization.
36*90c8c64dSAndroid Build Coastguard Worker
37*90c8c64dSAndroid Build Coastguard Worker        Args:
38*90c8c64dSAndroid Build Coastguard Worker            path: path to the fastboot executable to test with.
39*90c8c64dSAndroid Build Coastguard Worker
40*90c8c64dSAndroid Build Coastguard Worker        Raises:
41*90c8c64dSAndroid Build Coastguard Worker            FastbootError: Failed to find a device in fastboot mode.
42*90c8c64dSAndroid Build Coastguard Worker        """
43*90c8c64dSAndroid Build Coastguard Worker        self.path = path
44*90c8c64dSAndroid Build Coastguard Worker
45*90c8c64dSAndroid Build Coastguard Worker        # Make sure the fastboot executable is available.
46*90c8c64dSAndroid Build Coastguard Worker        try:
47*90c8c64dSAndroid Build Coastguard Worker            _subprocess_check_output([self.path, '--version'])
48*90c8c64dSAndroid Build Coastguard Worker        except OSError:
49*90c8c64dSAndroid Build Coastguard Worker            raise FastbootError('Could not execute `{}`'.format(self.path))
50*90c8c64dSAndroid Build Coastguard Worker
51*90c8c64dSAndroid Build Coastguard Worker        # Make sure exactly 1 fastboot device is available if <specific device>
52*90c8c64dSAndroid Build Coastguard Worker        # was not given as an argument. Do not try to find an adb device and
53*90c8c64dSAndroid Build Coastguard Worker        # put it in fastboot mode, it would be too easy to accidentally
54*90c8c64dSAndroid Build Coastguard Worker        # download to the wrong device.
55*90c8c64dSAndroid Build Coastguard Worker        if not self._check_single_device():
56*90c8c64dSAndroid Build Coastguard Worker            raise FastbootError('Requires exactly 1 device in fastboot mode')
57*90c8c64dSAndroid Build Coastguard Worker
58*90c8c64dSAndroid Build Coastguard Worker    def _check_single_device(self):
59*90c8c64dSAndroid Build Coastguard Worker        """Returns True if there is exactly one fastboot device attached.
60*90c8c64dSAndroid Build Coastguard Worker           When ANDROID_SERIAL is set it checks that the device is available.
61*90c8c64dSAndroid Build Coastguard Worker        """
62*90c8c64dSAndroid Build Coastguard Worker
63*90c8c64dSAndroid Build Coastguard Worker        if 'ANDROID_SERIAL' in os.environ:
64*90c8c64dSAndroid Build Coastguard Worker            try:
65*90c8c64dSAndroid Build Coastguard Worker                self.getvar('product')
66*90c8c64dSAndroid Build Coastguard Worker                return True
67*90c8c64dSAndroid Build Coastguard Worker            except subprocess.CalledProcessError:
68*90c8c64dSAndroid Build Coastguard Worker                return False
69*90c8c64dSAndroid Build Coastguard Worker        devices = _subprocess_check_output([self.path, 'devices']).splitlines()
70*90c8c64dSAndroid Build Coastguard Worker        return len(devices) == 1 and devices[0].split()[1] == 'fastboot'
71*90c8c64dSAndroid Build Coastguard Worker
72*90c8c64dSAndroid Build Coastguard Worker    def getvar(self, name):
73*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot getvar`.
74*90c8c64dSAndroid Build Coastguard Worker
75*90c8c64dSAndroid Build Coastguard Worker        To query all variables (fastboot getvar all) use getvar_all()
76*90c8c64dSAndroid Build Coastguard Worker        instead.
77*90c8c64dSAndroid Build Coastguard Worker
78*90c8c64dSAndroid Build Coastguard Worker        Args:
79*90c8c64dSAndroid Build Coastguard Worker            name: variable name to access.
80*90c8c64dSAndroid Build Coastguard Worker
81*90c8c64dSAndroid Build Coastguard Worker        Returns:
82*90c8c64dSAndroid Build Coastguard Worker            String value of variable |name| or None if not found.
83*90c8c64dSAndroid Build Coastguard Worker        """
84*90c8c64dSAndroid Build Coastguard Worker        try:
85*90c8c64dSAndroid Build Coastguard Worker            output = _subprocess_check_output([self.path, 'getvar', name],
86*90c8c64dSAndroid Build Coastguard Worker                                             stderr=subprocess.STDOUT).splitlines()
87*90c8c64dSAndroid Build Coastguard Worker        except subprocess.CalledProcessError:
88*90c8c64dSAndroid Build Coastguard Worker            return None
89*90c8c64dSAndroid Build Coastguard Worker        # Output format is <name>:<whitespace><value>.
90*90c8c64dSAndroid Build Coastguard Worker        out = 0
91*90c8c64dSAndroid Build Coastguard Worker        if output[0] == "< waiting for any device >":
92*90c8c64dSAndroid Build Coastguard Worker            out = 1
93*90c8c64dSAndroid Build Coastguard Worker        result = re.search(r'{}:\s*(.*)'.format(name), output[out])
94*90c8c64dSAndroid Build Coastguard Worker        if result:
95*90c8c64dSAndroid Build Coastguard Worker            return result.group(1)
96*90c8c64dSAndroid Build Coastguard Worker        else:
97*90c8c64dSAndroid Build Coastguard Worker            return None
98*90c8c64dSAndroid Build Coastguard Worker
99*90c8c64dSAndroid Build Coastguard Worker    def getvar_all(self):
100*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot getvar all`.
101*90c8c64dSAndroid Build Coastguard Worker
102*90c8c64dSAndroid Build Coastguard Worker        Returns:
103*90c8c64dSAndroid Build Coastguard Worker            A {name, value} dictionary of variables.
104*90c8c64dSAndroid Build Coastguard Worker        """
105*90c8c64dSAndroid Build Coastguard Worker        output = _subprocess_check_output([self.path, 'getvar', 'all'],
106*90c8c64dSAndroid Build Coastguard Worker                                         stderr=subprocess.STDOUT).splitlines()
107*90c8c64dSAndroid Build Coastguard Worker        all_vars = {}
108*90c8c64dSAndroid Build Coastguard Worker        for line in output:
109*90c8c64dSAndroid Build Coastguard Worker            result = re.search(r'(.*):\s*(.*)', line)
110*90c8c64dSAndroid Build Coastguard Worker            if result:
111*90c8c64dSAndroid Build Coastguard Worker                var_name = result.group(1)
112*90c8c64dSAndroid Build Coastguard Worker
113*90c8c64dSAndroid Build Coastguard Worker                # `getvar all` works by sending one INFO message per variable
114*90c8c64dSAndroid Build Coastguard Worker                # so we need to strip out the info prefix string.
115*90c8c64dSAndroid Build Coastguard Worker                if var_name.startswith(self.INFO_PREFIX):
116*90c8c64dSAndroid Build Coastguard Worker                    var_name = var_name[len(self.INFO_PREFIX):]
117*90c8c64dSAndroid Build Coastguard Worker
118*90c8c64dSAndroid Build Coastguard Worker                # In addition to returning all variables the bootloader may
119*90c8c64dSAndroid Build Coastguard Worker                # also think it's supposed to query a return a variable named
120*90c8c64dSAndroid Build Coastguard Worker                # "all", so ignore this line if so. Fastboot also prints a
121*90c8c64dSAndroid Build Coastguard Worker                # summary line that we want to ignore.
122*90c8c64dSAndroid Build Coastguard Worker                if var_name != 'all' and 'total time' not in var_name:
123*90c8c64dSAndroid Build Coastguard Worker                    all_vars[var_name] = result.group(2)
124*90c8c64dSAndroid Build Coastguard Worker        return all_vars
125*90c8c64dSAndroid Build Coastguard Worker
126*90c8c64dSAndroid Build Coastguard Worker    def flashall(self, wipe_user=True, slot=None, skip_secondary=False, quiet=True):
127*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot [-w] flashall`.
128*90c8c64dSAndroid Build Coastguard Worker
129*90c8c64dSAndroid Build Coastguard Worker        Args:
130*90c8c64dSAndroid Build Coastguard Worker            wipe_user: whether to set the -w flag or not.
131*90c8c64dSAndroid Build Coastguard Worker            slot: slot to flash if device supports A/B, otherwise default will be used.
132*90c8c64dSAndroid Build Coastguard Worker            skip_secondary: on A/B devices, flashes only the primary images if true.
133*90c8c64dSAndroid Build Coastguard Worker            quiet: True to hide output, false to send it to stdout.
134*90c8c64dSAndroid Build Coastguard Worker        """
135*90c8c64dSAndroid Build Coastguard Worker        func = (_subprocess_check_output if quiet else subprocess.check_call)
136*90c8c64dSAndroid Build Coastguard Worker        command = [self.path, 'flashall']
137*90c8c64dSAndroid Build Coastguard Worker        if slot:
138*90c8c64dSAndroid Build Coastguard Worker            command.extend(['--slot', slot])
139*90c8c64dSAndroid Build Coastguard Worker        if skip_secondary:
140*90c8c64dSAndroid Build Coastguard Worker            command.append("--skip-secondary")
141*90c8c64dSAndroid Build Coastguard Worker        if wipe_user:
142*90c8c64dSAndroid Build Coastguard Worker            command.append('-w')
143*90c8c64dSAndroid Build Coastguard Worker        func(command, stderr=subprocess.STDOUT)
144*90c8c64dSAndroid Build Coastguard Worker
145*90c8c64dSAndroid Build Coastguard Worker    def flash(self, partition='cache', img=None, slot=None, quiet=True):
146*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot flash`.
147*90c8c64dSAndroid Build Coastguard Worker
148*90c8c64dSAndroid Build Coastguard Worker        Args:
149*90c8c64dSAndroid Build Coastguard Worker            partition: which partition to flash.
150*90c8c64dSAndroid Build Coastguard Worker            img: path to .img file, otherwise the default will be used.
151*90c8c64dSAndroid Build Coastguard Worker            slot: slot to flash if device supports A/B, otherwise default will be used.
152*90c8c64dSAndroid Build Coastguard Worker            quiet: True to hide output, false to send it to stdout.
153*90c8c64dSAndroid Build Coastguard Worker        """
154*90c8c64dSAndroid Build Coastguard Worker        func = (_subprocess_check_output if quiet else subprocess.check_call)
155*90c8c64dSAndroid Build Coastguard Worker        command = [self.path, 'flash', partition]
156*90c8c64dSAndroid Build Coastguard Worker        if img:
157*90c8c64dSAndroid Build Coastguard Worker            command.append(img)
158*90c8c64dSAndroid Build Coastguard Worker        if slot:
159*90c8c64dSAndroid Build Coastguard Worker            command.extend(['--slot', slot])
160*90c8c64dSAndroid Build Coastguard Worker        if skip_secondary:
161*90c8c64dSAndroid Build Coastguard Worker            command.append("--skip-secondary")
162*90c8c64dSAndroid Build Coastguard Worker        func(command, stderr=subprocess.STDOUT)
163*90c8c64dSAndroid Build Coastguard Worker
164*90c8c64dSAndroid Build Coastguard Worker    def reboot(self, bootloader=False):
165*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot reboot [bootloader]`.
166*90c8c64dSAndroid Build Coastguard Worker
167*90c8c64dSAndroid Build Coastguard Worker        Args:
168*90c8c64dSAndroid Build Coastguard Worker            bootloader: True to reboot back to the bootloader.
169*90c8c64dSAndroid Build Coastguard Worker        """
170*90c8c64dSAndroid Build Coastguard Worker        command = [self.path, 'reboot']
171*90c8c64dSAndroid Build Coastguard Worker        if bootloader:
172*90c8c64dSAndroid Build Coastguard Worker            command.append('bootloader')
173*90c8c64dSAndroid Build Coastguard Worker        _subprocess_check_output(command, stderr=subprocess.STDOUT)
174*90c8c64dSAndroid Build Coastguard Worker
175*90c8c64dSAndroid Build Coastguard Worker    def set_active(self, slot):
176*90c8c64dSAndroid Build Coastguard Worker        """Calls `fastboot set_active <slot>`.
177*90c8c64dSAndroid Build Coastguard Worker
178*90c8c64dSAndroid Build Coastguard Worker        Args:
179*90c8c64dSAndroid Build Coastguard Worker            slot: The slot to set as the current slot."""
180*90c8c64dSAndroid Build Coastguard Worker        command = [self.path, 'set_active', slot]
181*90c8c64dSAndroid Build Coastguard Worker        _subprocess_check_output(command, stderr=subprocess.STDOUT)
182*90c8c64dSAndroid Build Coastguard Worker
183*90c8c64dSAndroid Build Coastguard Worker# If necessary, modifies subprocess.check_output() or subprocess.Popen() args
184*90c8c64dSAndroid Build Coastguard Worker# to run the subprocess via Windows PowerShell to work-around an issue in
185*90c8c64dSAndroid Build Coastguard Worker# Python 2's subprocess class on Windows where it doesn't support Unicode.
186*90c8c64dSAndroid Build Coastguard Workerdef _get_subprocess_args(args):
187*90c8c64dSAndroid Build Coastguard Worker    # Only do this slow work-around if Unicode is in the cmd line on Windows.
188*90c8c64dSAndroid Build Coastguard Worker    # PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
189*90c8c64dSAndroid Build Coastguard Worker    # very slow.
190*90c8c64dSAndroid Build Coastguard Worker    if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
191*90c8c64dSAndroid Build Coastguard Worker        return args
192*90c8c64dSAndroid Build Coastguard Worker
193*90c8c64dSAndroid Build Coastguard Worker    def escape_arg(arg):
194*90c8c64dSAndroid Build Coastguard Worker        # Escape for the parsing that the C Runtime does in Windows apps. In
195*90c8c64dSAndroid Build Coastguard Worker        # particular, this will take care of double-quotes.
196*90c8c64dSAndroid Build Coastguard Worker        arg = subprocess.list2cmdline([arg])
197*90c8c64dSAndroid Build Coastguard Worker        # Escape single-quote with another single-quote because we're about
198*90c8c64dSAndroid Build Coastguard Worker        # to...
199*90c8c64dSAndroid Build Coastguard Worker        arg = arg.replace(u"'", u"''")
200*90c8c64dSAndroid Build Coastguard Worker        # ...put the arg in a single-quoted string for PowerShell to parse.
201*90c8c64dSAndroid Build Coastguard Worker        arg = u"'" + arg + u"'"
202*90c8c64dSAndroid Build Coastguard Worker        return arg
203*90c8c64dSAndroid Build Coastguard Worker
204*90c8c64dSAndroid Build Coastguard Worker    # Escape command line args.
205*90c8c64dSAndroid Build Coastguard Worker    argv = map(escape_arg, args[0])
206*90c8c64dSAndroid Build Coastguard Worker    # Cause script errors (such as adb not found) to stop script immediately
207*90c8c64dSAndroid Build Coastguard Worker    # with an error.
208*90c8c64dSAndroid Build Coastguard Worker    ps_code = u'$ErrorActionPreference = "Stop"\r\n'
209*90c8c64dSAndroid Build Coastguard Worker    # Add current directory to the PATH var, to match cmd.exe/CreateProcess()
210*90c8c64dSAndroid Build Coastguard Worker    # behavior.
211*90c8c64dSAndroid Build Coastguard Worker    ps_code += u'$env:Path = ".;" + $env:Path\r\n'
212*90c8c64dSAndroid Build Coastguard Worker    # Precede by &, the PowerShell call operator, and separate args by space.
213*90c8c64dSAndroid Build Coastguard Worker    ps_code += u'& ' + u' '.join(argv)
214*90c8c64dSAndroid Build Coastguard Worker    # Make the PowerShell exit code the exit code of the subprocess.
215*90c8c64dSAndroid Build Coastguard Worker    ps_code += u'\r\nExit $LastExitCode'
216*90c8c64dSAndroid Build Coastguard Worker    # Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
217*90c8c64dSAndroid Build Coastguard Worker    # understands.
218*90c8c64dSAndroid Build Coastguard Worker    ps_code = ps_code.encode('utf-16le')
219*90c8c64dSAndroid Build Coastguard Worker
220*90c8c64dSAndroid Build Coastguard Worker    # Encode the PowerShell command as base64 and use the special
221*90c8c64dSAndroid Build Coastguard Worker    # -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
222*90c8c64dSAndroid Build Coastguard Worker    # so it should have no problem passing through Win32 CreateProcessA()
223*90c8c64dSAndroid Build Coastguard Worker    # (which python erroneously calls instead of CreateProcessW()).
224*90c8c64dSAndroid Build Coastguard Worker    return (['powershell.exe', '-NoProfile', '-NonInteractive',
225*90c8c64dSAndroid Build Coastguard Worker             '-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]
226*90c8c64dSAndroid Build Coastguard Worker
227*90c8c64dSAndroid Build Coastguard Worker# Call this instead of subprocess.check_output() to work-around issue in Python
228*90c8c64dSAndroid Build Coastguard Worker# 2's subprocess class on Windows where it doesn't support Unicode.
229*90c8c64dSAndroid Build Coastguard Workerdef _subprocess_check_output(*args, **kwargs):
230*90c8c64dSAndroid Build Coastguard Worker    try:
231*90c8c64dSAndroid Build Coastguard Worker        return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
232*90c8c64dSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
233*90c8c64dSAndroid Build Coastguard Worker        # Show real command line instead of the powershell.exe command line.
234*90c8c64dSAndroid Build Coastguard Worker        raise subprocess.CalledProcessError(e.returncode, args[0],
235*90c8c64dSAndroid Build Coastguard Worker                                            output=e.output)
236